Clean architecture has been around for some time but as with all ‘best practice’ it’s get interpreted in as many different ways as there are programmers. At it’s heart Clean Architecture is a complexity and change management approach to organising code. Coined by the now famous “Uncle Bob”. The approach has been met with both praise and criticism. Both camps have valid points but as with all patterns and practices, a good slice of pragmatism to go along with your kool aid keeps things down to earth.
Why do we need it?
If you’ve been involved with software in a commercial environment, it’s guaranteed you’ve seen a project start well and show promise in its direction. Maybe the code was written well, the team applied unit testing, ran a tight agile ship, drove requirements through BDD, used the very newest technology and leant on trusted sources of learning to “skill up”.
Or maybe someone has made a side project that just ‘works’ and should be easy to stick in production.
Then maybe a few months in, or even weeks, the code starts to get a bit awkward. Somehow making changes to the code gets harder and harder. Merges get more and more awkward and the ability to stay Agile or Pivot to the customer requirements starts to break down.
We’ve all seen it, and it happens every day in the software industry. The causes can be complicated and manifold but more than likely the root cause is a lack of focus on architecture.
Layers, so many layers
There are many forms of architecture that follow the same principles of layering. Separating your code into some sort of organised pattern of layers that allows you to interchange those layers when needed. Layering allows you to push technology decisions and implementation details out to the periphery of your application where they can be changed, altered or added as needed without breaking your business logic.
Clean Architecture, Onion, Hexagonal, DDD, repeat until google dries up…..
The point is that picking one architecture and sticking to it is a key element of avoiding technical debt, or at least being able to address it as it builds up. It should be obvious by now we are going to go over Clean Architecture, but this article isn’t to say it’s the best or that others aren’t valid.
We are going to look at how picking an architecture and wrapping your chosen technology around it lets you deliver faster, with less headaches and stay hyper agile to customer needs.
The basics of Clean Architecture
Clean Architecture was coined by Robert Martin, famously called ‘UncleBob’. Bob has a number of pivotal books on coding over the years, but he is best known for his thoughts on ‘Clean’ code.
- 1995. Designing Object-Oriented C++ Applications Using the Booch Method. Prentice Hall. ISBN 978-0132038379.
- 2002. Agile Software Development, Principles, Patterns, and Practices. Pearson. ISBN 978-0135974445.
- 2009. Clean Code: A Handbook of Agile Software Craftsmanship. Prentice Hall. ISBN 978-0132350884.
- 2011. The Clean Coder: A Code Of Conduct For Professional Programmers. Prentice Hall. ISBN 978-0137081073.
- 2017. Clean Architecture: A Craftsman’s Guide to Software Structure and Design. Prentice Hall. ISBN 978-0134494166.
- 2019. Clean Agile: Back to Basics. Prentice Hall. ISBN 978-0135781869.
As mentioned above Clean Architecture essentially splits your code into separate layers with different responsibilities.
- The objects that are at the core of your business logic. Think Users, Locations, Books, etc. The meat and potatoes of OOP
- The essential domain logic that your entities must follow. A User must have a name, a Location must have latitude and longitude, etc
Use Case layer
- The application logic of the system. The Use Cases are the ways in which outside layers can use and interact with the entities
- Add a user to the system
- Update a user’s location
- Search for books of a similar type
- This layer also contains the interface definitions for the repositories that will store the entities. The Use Cases accept concrete implementations of the interfaces, but the implementations DO NOT get created in this layer
- Is responsible for taking data and instructions that are suitable for the outer layers and converting them to those suitable for the Use Case layer. Essentially mapping between the outer layers and the Use Cases
- These are the implementation details of your application
- Repository implementations to work with your chosen database technology
- The User Interface
- Connections to other APIs and systems
The key thing that makes this architecture (and others like it) so powerful is the direction of dependencies. Layers can only reference and work with other objects that are inside their own layer or that lie inwards of their layer. This is a key concept to understand as it drives all the benefits of decoupling and maintainability.
Let’s draw a picture! We’re going to present a Clean approach to a using Googles Flutter and Firebase stack. As we’ve mentioned the technologies do not matter, you need to bend the technologies to your will and wrap them around a sensible architecture!
Taking a fairly straight forward task of signing up users, logging them in and letting them update their profile we can split the code across out Clean Architecture.
- Straight forward User object, let’s say there is some domain logic in there which says the User must have a valid email address
- Repository interfaces for Create, Retrieve and Update of Users
- Use Cases containing the application logic for
- Signing up a new User
- Logging in a new User
- Retrieving a User Account
- Updating a User’s details
- These Use Cases have the concrete repositories injected into their constructors meaning they ONLY reference the repository interfaces
- In this case we are using BLoCs (we blogged about BLoC pattern before) to be the parts that take the UI instructions and data then convert them into Use Case operations
- We’ve got a BLoC per UI ‘Page’ of Login and User Profile
- Contains the UI implementation, in this example Flutter
- Contains the Repository implementation, here we are using Firebase
- Objects in this layer do the Dependency Injection (in its simplest form via constructors)
How does this help?
If you’re still with me this far you might well be thinking “wow that’s a lot of moving parts to simply store an email in a DB” and you would be right. With that said there are several deep benefits to this approach.
- This approach bakes in SOLID coding
- Single Responsibility is promoted by making small chunks spread across the layers
- Open-close principle is promoted by keeping our business and application logic in the middle of our application. The closer to the centre the elements are the less likely they are to change and therefore less open for modification
- Liskov Substitution principle is promoted by having our interfaces defined inside the domain layers and the implementations are created outside and injected inwards. This allows us to change the repository type used (Firebase to SQL for example. There are more interfaces involved typically allowing deeper testing
- Dependency Inversion principle is adhered to. Layers can only refer to layers on the same level or inwards of themselves. For example the Use Cases have the repository implementations injected into them from the infrastructure layer
We briefly mentioned testability earlier but with the layers split and the units we’ve created all being fed by Dependency Injection it’s much easier to run our tests. We don’t have to do any special mocking or contortions to make our tests work, we simply implement some interfaces and pass them into our units under test.
By far the biggest positive for this approach is the speed at which we can change details of our application. Need to move from Flutter to React? No problem we change JUST the outer layer of the UI. Need to ditch Firebase because Google cranks up the cost? No problem we implement the repository interface to move to Mongo instead.
We’ve moved the units that change the least to the inside of our application because they are less likely to change. If they do need to change it should be obvious that there are going to be knock on effects through the outer layers. Though this ripple might cause work it’s likely to happen far less than those changes on the outside such as UI or Database changes.
We’ve only scratched the surface of Clean Architecture, but the benefits are clear. Though there is extra work and extra complication initially it pays off in the long run. Any application that isn’t trivial will, sooner or later, find growing pains and need to address technical debt. By preparing the way for change management early on we build in the flexibility to stay agile.