Clean Architecture Design Pattern
The key element is Dependency Inversion. None of the inner layers should have dependencies to the outer layers. So, if for example the Use Case layer needs to call a database repository then you must define a repository interface (just an interface, without any implementation) inside the Use Case layer and put its implementation in the Interface Adapters layer.
Integrating the database
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.
+-----+ +-----------+
| db | --> | use cases |
+-----+ +-----------+
It might be even better to extract these use cases interface into an own module. This prevents the db module to depent on dependencies of the use case module.
The module dependencies would then look like this
+-----+ +---------------+ +-----------+
| db | --> | use-cases-api | <-- | use cases |
+-----+ +---------------+ +-----------+
both options are the inversion of dependencies that would otherwise look like this
+-----+ +-----------+
| db | <--X-- | use cases |
+-----+ +-----------+
Integrating the web layer
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 the use case layer accesses a repository that can be a database repository, but also an arbitary external system.
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 other words you adapt the outer layer to the inner layer. I guess that is the reason why uncle bob calls this layer Interface Adapters
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.
So why should we do this effort?
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.
Robert C. Martin tells us, in chapter 5 Object-Oriented Programming at the end in the section about dependency inversion:
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. They 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!
Control flow and source code dependencies
I guess developers are often confused about the difference between the control flow and the source code dependency.
The control flow describes the order of invocations at runtime. The dependencies it introduces are called runtime dependencies.
The source code dependencies are, as the name implies, the dependencies of types that occur in your source code. In languages like Java types are imported. That is why the import statements tell you about almost all source code dependencies. I said almost all, because types within the same package don't need to be imported.
Dependency inversion means that the source code dependencies point against the flow of control. Dependency inversion gives us the chance to create plug-in architectures. Each interface is a point to plug in and that can be exchanged for domain, 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. One mistake that we often make is to think that an interface belongs to an implementor. But an interfacce belongs to a client. It's the client that tells what it wants with an interface, but keeps open how it is done.
Also consider to apply the interface segregation principle and define a use case specific interface such as a PlaceOrderUseCaseRepository
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. A repository interface that is dedicated to one use case has only one reason to change.
EDIT
To apply the interface segregation principle and provide an own repository interface that is dedicated to one use case will help to 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);
}