Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clean Architecture: Where to make API calls

I am currently creating a microservices project in which I implement the Clean Architecture pattern coined by Bob Martin. While my code works perfectly, I have a question about the clean architecture pattern, specifically the interfaces and use_cases layers. The application is a small eCommerce POC I am working on. That being said, since it is implementing microservices, I have 3 difference services. Products, Images, and Reviews. Whenever a request is made to get a "full product", the client will ping the endpoint for the full product, it will grab that, and use its id to ping my Images and Reviews services to get all images and reviews for that product.

My question, then, is where should I implement the logic to create these calls? My instinct tells me I should put it in the controller layer, as that is the most abstracted of the two, and I wouldn't feel so bad about putting the axios dependency inside of it. But alas, I also feel like the statement 'a full product MUST include the product details, its images, and all reviews' sounds a lot like a business rule.

I know this question is mostly subjective but I'd like to know how you'd implement this logic, and why?

I should also mention that in my current solution, my use_case is where I call the repository and actually grab the Product from the database. That being said, if I put the API calls into the controller layer I'd have to first call my use_case for obtaining a product and then possibly create a separate use_case for checking if my final object is actually a "full product".

like image 271
Brandon Miller Avatar asked Mar 02 '23 17:03

Brandon Miller


1 Answers

Obtaining the images and reviews from the perspective of your product use case is just like the database access. The only difference is that you don't query a database, you query another service instead. But from the perspective of your product microservice both are external systems that provide data to your use case.

When you take a look at the clean architecture you will realize that the controllers and gatways are on the same architectural layer - the interface adapters. This layer is named "interface adapters", because it adapts interfaces of the lower layer.

As you can see gateways can be a DB or an external interface (a service).

Gateways, DBs and external services of the clean architecture

Thus you should structure your application in this way:

                       +------------------+
                       | product use case |
                       +------------------+
                                |
             +------------------+---------------------+
             |                  |                     |
             V                  V                     V
    +-----------------+ +------------------+ +-------------------+
    | ImageRepository | | ReviewRepository | | ProductRepository |
    +-----------------+ +------------------+ +-------------------+
            ^                     ^                    ^
            |                     |                    |
 ===========+=====================+====================+================
            |                     |                    |
 +--------------------+ +--------------------+ +--------------------+
 |   ImageRestClient  | | ReviewRestClient   | |   JDBCConnector    |
 +--------------------+ +--------------------+ +--------------------+

You might want to choose another naming but the structure would remain the same.

Easy to test and if you decide one day that the images should be managed in the product microservice instead of a separate service you can replace the ImageRestClient with a JDBCConnector.


EDIT

@Rene, could you please help to understand. I use clean architecture a lot but so far I don't understand why DB layer is an outer circle in a clean architecture. Yes, it's a communication with an external system, but 1) if DB is a framework, usually the main Application will import DB, not vice versa.

It is an application of the dependency inversion principle that tells us that

"High level policy should not depend on low level details." - Robert. C. Martin

The way an application remembers data is a detail. I said "remembers", because this is the abstraction of a data store. Maybe the data is stored in a db, just a file or even only in RAM. E.g. a web application could use a SessionCartRepository or a DBCartRepository to store a shopping cart.

I guess people sometimes are a bit confused by the clean architecture, because the diagrams they saw before placed the DB in the center. But the clean architecture diagram is only a way to visualize how to structure your application.

Years before I heard of the clean architecture I already applied it, but my diagram looked like this.

 +--------------------+
 | ApplicationService | ---+
 +--------------------+    |
       |                   |        Use Case
 ======|===================|================
       V                   V          Domain
 +------------+      +----------------+
 | Repository | -->  | BusinessObject |
 +------------+      +----------------+
       ^
=======|====================================
       |                            Database
 +----------------+   
 | JdbcRepository | 
 +----------------+

Ok, in 2014 my naming was different. MyApplicationService implemented use cases and my BusinessObject was the Entity of the CA. But the structure was the same as the CA proposes. When I read Uncle Bob's book "The clean architecture" for the first time I though that his diagram is a lot better. Since then I use the CA diagram too. But sometimes I use the diagram I showed above, because some people like that the DB layer is drawn on the bottom for whatever reasons.

  1. When External Services has Network Models and UseCases have DomainModels, which layer should be responsible to map from Network Models into DomainModels (usually for me it was UseCases layer, but it means that inner layer knows about outer layer). Should External Services layer do that mapping

A component that maps between two types must know both types and therefore it has dependencies to both.

+------------+       +--------+       +------------+
| SourceType | <---- | Mapper | ----> | TargetType |
+------------+       +--------+       +------------+

Thus you can't place the mapping code in the entity or use case layer, because these layer would then have dependencies to the outer layer, e.g. the network models which are details. It would violate the dependency rules of the clean architecture.

As a result you must place the mapping code in the outer layer (e.g. network layer).

LAST EDIT

You mentioned that your diagram is a bit old as you used it before. So what is the correct way right now?

The way that Uncle Bob does, since it is the widely used one and only his view is named "Clean Architecture". Mine has no name.

If I understand correctly now 1) Repository should sit only in outer layer and only use cases can talk to Repositories (via abstraction of course)?

The repository implementations are placed in the outer layer. The definition, e.g. interfaces or abstract classes are placed in the use case layer. In other words a use case tells what it needs, e.g. with an interface. A provider like a database implements it.

  1. Domain layer is Entities in Clean Architecture? 3) Or should some Repos sit in a domain layer and talk to Repos in DB layer (outer layer) (so it would mean that UseCases don't talk to DB Repos)

This was my "old" view before I heard about the clean architecture. I usually put the repositories beside the use case. I also usually apply the interface segregation principle which means that I make use case specific repositories. E.g.

public interface PlaceOrderRepository {
   ...
}

Before I heard of the CA, I put the repository definitions as abstract classes in the domain layer so that I can use the package modifier. This is what I don't do today anymore. Today I put factories in the entity layer that can benefit from the package scope (if i need package scope) and let repositories use these factories.

Thus I recommend to define use case specific repositories in the use case use case layer and implement them in the outer layer - the interface adapters layer.

Interface adapters layer

I guess that is why this layer is called interface adapters, because it is the layer where you implement adapters for interfaces that are defined in an inner circle.

like image 92
René Link Avatar answered Mar 11 '23 18:03

René Link