I went to SfPot meetup at Pepinière 27 (website, Twitter) on second to last Friday with fellow colleagues Simon, Thierry, Kinga, and Paul.
One particular talk retained my attention and I want to tell you about it.
Let me warn you first, this is just an introduction, and I’m not going into much detail, don’t hesitate to post comments if you feel something is not clear, or deserves a better exposure!
Clean architecture (in French “Design applicatif avec symfony2”): slides by Romain Kuzniak (GitHub, Twitter), technical manager @openclassroms
So. What is Clean Architecture? It’s so fresh that it doesn’t even have a Wikipedia article.
Let’s start by what it’s trying to accomplish.
The aim of Clean Architecture is to favor change, by keeping the change cost of a product the most constant possible during the developement process.
In a few words, it’s an evolution of Domain Driven Design (a.k.a DDD), with more abstractions.
- Manage complex architectures, it probably isn’t suited to run a small personal blog.
- Be independent from the framework, the UI and the infrastructure, for versatility.
- Be highly testable.
- The domain is at the center of the application.
- Communication between application layers is done through abstractions.
- SOLID principles are followed.
- The architecture reveals its intentions.
D from DDD
This is Domain.
Business rules are carried by the elements of the domain.
In Symfony, this means we code the business logic directly inside the entities, this is the essence of DDD.
The reasoning behind this choice is that business rules should apply independently from the context, as opposed to applicative rules.
Let’s give an example to explain the difference, strongly inspired from Romain’s talk.
We want to build an agile board (Jira is a very good example).
In the domain there are two entities:
Issues have two different states, open or closed, and a sprint can also be either open or closed.
There are issues in a sprint.
Issues without sprint are in the backlog.
As a user, I want to be able to close a sprint, either manually through clicking a button on my browser, or automatically depending on the sprint previously filled closing date.
- If I close the sprint manually, I want to get a nice report in my browser telling me how the sprint went with graphs, and statistics.
- If it’s closed automatically, let’s say I only want the number of closed issues logged into a file.
- Either way, when the sprint is closed, remaining open issues in the sprint should be removed from it and automatically placed in the top of the backlog.
This last rule, represents a business rule.
Anytime a sprint is closed, it should be applied.
The two former rules on the contrary, strongly depending from the context, are applicative rules.
Why am I talking about DDD?
Domain takes care of business rules, closing a sprint would be the responsibility of the sprint itself.
But where do we put the applicative rules?
That’s where Clean Architecture comes to the rescue, it’s the next evolution.
A picture is worth a thousand words
Okay. Let’s see a diagram of what we’re talking about (again, from Romain):
This is Clean Architecture.
Wow. I mean. WOW. What are all these abstraction layers?
<I> denotes an interface whereas <A> denotes an abstract class.
Let’s talk about the red spot, it seems to be the important part.
Use Case is indeed the central point of Clean Architecture.
It’s the part describing how an applicative rule works.
Let’s describe its key characteristics with our agile board example:
|Specs of the Use Case||Illustration|
|It represents an applicative rule, and therefore is named after it||Use Case: My name is “Manually close the sprint”|
|It takes in input a Use Case request||Request to Use Case: I want to close sprint #9|
|It has only one method, execute||Use Case: Well, let’s close sprint #9 busy busy|
|It uses entities gateways to get the entities needed to work with||Use Case to Entity Gateway: I need the sprint #9 entity|
|It uses the entities to apply business rules||Use Case to Sprint: close yourself!|
|It answers with a Use Case response||Use Case Response: Here is the report, mate|
We go deeper (or higher depending on the point of view) in the abstraction.
The entity represents only the business rules associated to the Domain element.
No need to bother with data in the Use Case layer which is carried at the entity implementation level.
Fun fact, your entity implementation of a particular entity needs not to always be the same.
In some cases, you may find it relevant to implement it differently, for example, depending on if it comes from a database or an API call.
All these abstractions are here to ensure we captured the essence of the applicative rule.
It’s therefore completely framework, UI and infrastructure agnostic.
And, the use cases represent faithfully the functional features of our application.
As a bonus, with so much decoupling, it’s (nearly) a pleasure to write tests!
I won’t lie, there is a cost to it, we need a huge amount of classes to develop, so the feature cost is higher than in a simpler architecture.
But, the experience of Romain with this architecture set up at openclassrooms shows that the benefit is that the feature cost tends to increase really much more slowly over time.
The controller layer is in charge of getting the request, it can be a Symfony Controller, or a Command, or an API Controller. It’s in charge of sending requests to use cases.
The presenter is in charge of getting responses from use cases, and render views from it. Views can be HTML pages, a file, a printed document etc. In Symfony apps, the controller is in charge of this responsibility.
The only things allowed to go through the layer borders are simple DTO.
This ensures the maximum decoupling of the layers as they cannot transmit logic between them.
Can you stop using big words and show us some code?
Romain’s talk is far from being a job half done.
Here is the code used in his slides to present the difference between 4 architectures in a Symfony app context:
I can’t stress this enough, but the talk slides (in French) are amazing and provide great explanation for these examples.
I want to try it at home, or at work!
A warning first.
Be sure to understand that Clean Architecture is a tool, it’s not a miracle.
There are situations where this tool is not very fitted, and it’s important to know when you should use it.
The first thing you need is a complex architecture, to leverage the entry cost.
So if you’re developing a POC, now is probably not the best time to use Clean Architecture.
Be sure that complexity is inherent to your product and required for it to work.
Be sure to have a team ready for this (even a team of one!).
It requires discipline to write interfaces for nearly everything, to put the logic where it belongs, to respect the layers boundaries etc.
A strong review process and pair programming (and open minds!) help a lot to instill the architecture in everyone.
Be ready to dig into the mud.
Clean architecture is incredibly recent, and unstable.
There is neither a lot of literature, nor a lot of feedbacks on the subject.
Be ready to make it evolve, you will be part of the shaping, you will need to iterate.
Everything won’t be granted, and there is a chance of failure, so assess your risks carefully.
I’m sure, I really wanna use Clean Architecture. How do I start?
Great! Romain presented us a very nice PHP implementation of Clean Architecture he developed at openclassrooms.
I encourage you to try it and contribute to it.
This is just an introduction to Clean Architecture, and some concepts might not be obvious to everyone, so if you need, ask Romain, he’s a very nice person!
And for people using Symfony, try the bundle!