Welcome to the fifth installment of little architecture series! So far we have covered layers, hexagons, onions, and features. Today, we’ll look at a close friend of all four – Uncle Bob’s Clean Architecture, initially introduced here.
What Is Clean Architecture?
Clean Architecture builds upon the previously introduced four concepts and aligns the project with best practices like the Dependency Inversion Principle or Use Cases. It also aims for a maximum independence of any frameworks or tools that might stay in the way of application’s testability or their replacement.
Clean Architecture divides our system into four layers, usually represented by circles:
- Entities, which contain enterprise-wide business rules. You can think of them as about Domain Entities a la DDD.
- Use cases, which contain application-specific business rules. These would be counterparts to Application Services with the caveat that each class should focus on one particular Use Case.
- Interface adapters, which contain adapters to peripheral technologies. Here, you can expect MVC, Gateway implementations and the like.
- Frameworks and drivers, which contain tools like databases or framework. By default, you don’t code too much in this layer, but it’s important to clearly state the place and priority that those tools have in your architecture.
Between the circles, there is a strong dependency rule – no code in the inner circle can directly reference a piece of code from the outer circle. All outward communication should happen via interfaces. It’s exactly the same dependency rule as we introduced in the Onion Architecture post.
Apart from the layers, Clean Architecture gives us some tips about the classes we need to implement. As you can see in the picture below, the flow of control from the Controller to the Use Case goes through an Input Port interface and flows to the Presenter through an Output Port interface. This ensures that the Use Case and the user interface are properly decoupled. We’ll see an example of this later in the implementation section.
The Essence of Clean Architecture
I see two things that make Clean Architecture distinct and potentially more effective than other architectural styles: strong adherence to the Dependency Inversion Principle and Use Case orientation.
Strong Adherence to DIP
Similarly to its Onion cousin, Clean Architecture introduces the Dependency Inversion Principle at the architectural level. This way, it explicitly states the priorities between different kinds of objects in your system. In a way, Clean Architecture does a better job at this, as it leaves no doubt about the tools like frameworks or databases – they have a dedicated layer outside all others.
Use Case Orientation
Similarly to what we’ve seen in Package by Feature, Clean Architecture promotes vertical slicing of the code and leaving the layers mostly at the class level. The major difference between the two is that instead of focusing on a blurry concept of a feature, it reorients the packaging towards Use Cases. This is important as, ultimately, in any application that has some sort of GUI, one could identify real Use Cases. It’s also important to note that entities sit in a different layer as in complex systems, one Use Case can orchestrate several entities to cooperate and categorizing it by the type of the entity would be artificial.
Implementing Clean Architecture
We can’t be 100% sure about how Uncle Bob would implement a Clean Architecture today, as his book about it comes out in July (I preordered it already and will do a review or rehash of this post then), but we can look at the GitHub repository of his Clean Code Case study:
As you can see, the Entities and Use Cases layers have their own separate packages, while the other layers can be identified only conceptually. The
view packages can be considered a part of the Frameworks and Drivers, while gateways package is a little bit ambiguous – their implementation is surely the Interface Adapters layer, but their interfaces conceptually belong to Use Cases. My guess is that the interfaces are extracted to a separate package so they can be shared between different Use Cases. But it’s just a guess!
By looking at the
usecases.codecastSummeries, we can get more insight into how a complete Use Case package looks like. As you can see, it accommodates all classes related to the execution of a particular Use Case: the view, controller, presenter, boundaries, view and response models, and the Use Case class itself. This might be a lot more classes than you usually see in your projects when you execute an Application Service, but that’s what it takes to go perfectly Clean.
If you dug deeper into the implementation of the project’s classes, you’d see no annotation there other than @Override. That’s because of the frameworks being at the very outer layer of the architecture – the code is not allowed to reference them directly. You might ask, how could I leverage Spring in such a project? Well, if you really wanted to, you’d have to do some XML configurations or do it using @Configuration and @Bean classes. No @Service, no @Autowired, sorry!
My Extra Advice
Pulling off a Clean Architecture might be a demanding task, especially if you worked with a Package by Layer, fat controllers kind of project before. And even if you get the idea and necessary skills to implement it, your colleagues might not. They might want to do this anyway, but simply forget to add interfaces or work around framework annotations when necessary. One way to prevent some of these issues could be to create a Maven module for each of the layers so that breaking a rule won’t even compile. At the same time, if you don’t have these already, introducing Pair Programming or Code Reviews will help you to prevent people from messing up with the dependency declarations (circular dependencies would not work, but adding Spring both to the Use Cases and Adapters module would!).
Benefits of a Clean Architecture
- Screaming – Use Cases are clearly visible in the project’s structure
- Flexible – you should be able to switch frameworks, databases or application servers like pairs of gloves
- Testable – all the interfaces around let you setup the test scope and outside interactions in any way you want
- Could play well with best practices like DDD – to be honest, I haven’t seen it so far, but I also don’t see anything stopping you from making an effective mix of DDD’s Strategic and Tactical Patterns with Clean Architecture
Drawbacks of Clean Architecture
- No Idiomatic Framework Usage – the dependency rule is relentless in this area
- Learning Curve – it’s harder to grasp than the other styles, especially considering the point above
- Indirect – there will be a lot more interfaces than one might expect (I don’t see it as necessarily bad, but I’ve seen people pointing this out)
- Heavy – in the sense that you might end up with a lot more classes than you currently have in your projects (again, the extra classes are not necessarily bad)
When to Use Clean Architecture
Before I say something, let me note that I haven’t tried implementing it in a professional context yet, so all of it is a gut feeling. We will probably get some more knowledgeable advice from Uncle Bob himself in his upcoming book.
If we consider Clean Architecture‘s biggest drawbacks and its essence, I would derive the following criteria to consider:
- Is the team skilled and/or convinced enough? One might consider this lame, but if people just don’t get it or they don’t want to do this, imposing the rigor of Clean Architecture on them might be counter-productive.
- Will the system outlive major framework releases? Since we’re talking about heavy technology flexibility here, it’s important to consider if we’ll ever capitalize on this benefit. My experience so far suggests that most systems will, even if the developers won’t be in the company by then.
- Will the system outlive the developers and stakeholders employment? Since Clean Architecture is so sound and makes Use Cases so clearly visible, systems that follow its principles will be much simpler to comprehend in the code, even if those who wrote it and asked for it are already gone.
Clean Architecture looks like a very carefully thought and effective architecture. It makes the big leap of recognizing the mismatch between Use Cases and Entities and puts the former in the driving seat of our system. It also gives a clear place for Frameworks and Drivers in our system – a separate layer outside all other layers. This, combined with the dependency rule might give us a plethora of benefits, but also might be way harder to pull off. In the end, it boils down to the question whether the system will live long enough so that the investment returns.