https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html
I have some question about this pattern. The Database is at outter Layer but how would that work in reality? For example if i have a Microservices that just manges this entity:
person{
id,
name,
age
}
And one of the use cases would be manage Persons. Manage Persons is saving / retrieving / .. Persons (=> CRUD operations), but to do this the Usecase needs to talk to a database. But that would be a violation of the Dependency rule
The overriding rule that makes this architecture work is The Dependency Rule. This rule says that source code dependencies can only point inwards.
If i get a GET /person/{id}
Request should my Microservices process it like this?
But using Dependency Inversion would be a Violation of
Nothing in an inner circle can know anything at all about something in an outer circle. In particular, the name of something declared in an outer circle must not be mentioned by the code in an inner circle. That includes, functions, classes. variables, or any other named software entity.
Crossing boundaries. At the lower right of the diagram is an example of how we cross the circle boundaries. It shows the Controllers and Presenters communicating with the Use Cases in the next layer. Note the flow of control. It begins in the controller, moves through the use case, and then winds up executing in the presenter. Note also the source code dependencies. Each one of them points inwards towards the use cases.
We usually resolve this apparent contradiction by using the Dependency Inversion Principle. In a language like Java, for example, we would arrange interfaces and inheritance relationships such that the source code dependencies oppose the flow of control at just the right points across the boundary.
For example, consider that the use case needs to call the presenter. However, this call must not be direct because that would violate The Dependency Rule: No name in an outer circle can be mentioned by an inner circle. So we have the use case call an interface (Shown here as Use Case Output Port) in the inner circle, and have the presenter in the outer circle implement it.
The same technique is used to cross all the boundaries in the architectures. We take advantage of dynamic polymorphism to create source code dependencies that oppose the flow of control so that we can conform to The Dependency Rule no matter what direction the flow of control is going in.
Should the Use Case layer Declare an Repository Interface which will be implemented by the DB Package (Frameworks & Drivers Layer)
If the Server recieves a GET /persons/1
Request the PersonRest would create a PersonRepository and would pass this Repository + the ID to the ManagePerson::getPerson Function, getPerson doesnt know PersonRepository but knows the interface it implements so its doesnt violate any rules right?
ManagePerson::getPerson would use that Repository to look for the entity and would return a Person Entity to PersonRest::get which would return a Json Objekt to the Client right?
English is sadly not my native language so i hope you guys can let me know if i understood the pattern correct and maybe answer some of my questions.
Ty in advance
What is clean architecture? Clean architecture is a category of software design pattern for software architecture that follows the concepts of clean code and implements SOLID principles.
Clean architecture is a software design philosophy that separates the elements of a design into ring levels. An important goal of clean architecture is to provide developers with a way to organize code in such a way that it encapsulates the business logic but keeps it separate from the delivery mechanism.
Clean architecture vs. The logical layers of this style are as follows: Presentation layer ( accounts for the presentation to the user) Business logic layer (contains the business rules) Data access layer (processes the communication with the database)
First let me cover what Clean Architecture is, for those unfamiliar. At the very middle is Entities that represent the core of your domain. This is where your business rules (and/or domain model) should live. A layer outside of that is Use cases, this is your application layer that is invoking your domain.
The Database is at outter Layer but how would that work in reality?
You create a technology independent interface in the use case layer and implement it in the gateway layer. I guess that's why that layer is called interface adapters, because you adapt interfaces defined in an inner layer here. E.g.
public interface OrderRepository {
public List<Order> findByCustomer(Customer customer);
}
implementation is in the gateway layer
public class HibernateOrderRepository implements OrderRepository {
...
}
At runtime you pass the implementation instance to the use case's constructor. Since the use case only has a dependency to the interface, OrderRepository
in the example above, you don't have a source code dependency to the gateway implementation.
You can see this by scanning your import statements.
And one of the use cases would be manage Persons. Manage Persons is saving / retrieving / .. Persons (=> CRUD operations), but to do this the Usecase needs to talk to a database. But that would be a violation of the Dependency rule
No, that would not violate the dependency rule, because the use cases define the interface they need. The db just implements it.
If you manage your application dependencies with maven you will see that the db jar module depends on the use cases not vice versa. But it would be even better to extract these use cases interface into an own module.
Then the module dependencies would look like this
+-----+ +---------------+ +-----------+
| db | --> | use-cases-api | <-- | use cases |
+-----+ +---------------+ +-----------+
that's the inversion of dependencies that would otherwise look like this
+-----+ +-----------+
| db | <-- | use cases |
+-----+ +-----------+
If i get a GET /person/{id} Request should my Microservices process it like this?
Yes that would be a violation, because web layer accesses the db layer. A better approach is that the web layer accesses the controller layer, which accesses the use case layer and so on.
To keep the dependency inversion you must decouple the layers using interfaces like I showed above.
So if you want to pass data to an inner layer you must introduce an interface in the inner layer that defines methods to get the data it needs and implement it in the outer layer.
In the controller layer you will specify an interface like this
public interface ControllerParams {
public Long getPersonId();
}
in the web layer you might implement your service like this
@Path("/person")
public PersonRestService {
// Maybe injected using @Autowired if you are using spring
private SomeController someController;
@Get
@Path("{id}")
public void getPerson(PathParam("id") String id){
try {
Long personId = Long.valueOf(id);
someController.someMethod(new ControllerParams(){
public Long getPersonId(){
return personId;
}
});
} catch (NumberFormatException e) {
// handle it
}
}
}
At the first sight it seems to be boilerplate code. But keep in mind that you can let the rest framework deserialize the request into a java object. And this object might implement ControllerParams
instead.
If you consequently follow the dependency inversion rule and the clean architecture you will never see an import statement of a outer layer's class in an inner layer.
The purpose of the clean architecture is that the main business classes do not depend on any technology or environment. Since the dependencies point from outer to inner layers, the only reason for an outer layer to change is because of inner layer changes. Or if you exchange the outer layer's implementation technology. E.g. Rest -> SOAP
So why should we do this effort?
Robert C. Martin tells it in chapter 5 Object-Oriented Programming. At the end in the section dependency inversion he says:
With this approach, software architects working in systems written in OO languages have absolute control over the direction of all source code dependencies in the system. Thay are not constrained to align those dependencies with the flow of control. No matter which module does the calling and which module is called, the software architect can point the source code dependency in either direction.
That is power!
I guess developers are often confused about the control flow and the source code dependency. The control flow usually remains the same, but the source code dependencies are inversed. This gives us the chance to create plug-in architectures. Each interface is a point to plug in. So it can be exchanged, e.g. for technical or testing reasons.
EDIT
gateway layer = interface OrderRepository => shouldnt the OrderRepository-Interface be inside of UseCases because i need to use the crud operations on that level?
Yes, the OrderRepository interface should be definied in the use case layer. Also consider to apply the interface segregation principle and define a MyCuseCaseRepository
interface, instead of just a OrderRepository
that every use case uses.
The reason why you should do this is to prevent use cases from being coupled through a common interface and to honor the single responsibility principle. Because a repository interface that is dedicated to one use case has only one reason to change.
EDIT
It is also a good idea to apply the interface segregation principle and provide an own repository interface that is dedicated to the use case. This will help decouple use cases from each other. If all use cases use the same Repository interface, then this interface accumulates all the methods of all use cases. You can easily break one use case by changing a method of this interface.
So I usually apply the interface segregation principle and create repository interfaces named after the use case. E.g.
public interface PlaceOrderRepository {
public void storeOrder(Order order);
}
and another use case's interface might look like this:
public interface CancelOrderRepository {
public void removeOrder(Order order);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With