At OnceHub, we recently had a whirlwind tour of different architecture concepts and techniques. While researching the various techniques, I noticed that Clean Architecture, also known as Onion Architecture or Hexagonal Architecture, or Vertical Slice Architecture are sometimes simplified to the point of being a bit misleading, resulting in architectures which the Clean Architecture is trying to prevent.
Making a business’s solution accessible in any technological format should not require rewriting significant parts of the application. As appointments move from the physical to the virtual, a clean architecture allows for minimal disruption to core business use cases.
Often, the first exposure most people have to clean architecture is the main circular diagram explaining the high-level concepts of different architectural layers.
Here is the main diagram as presented by Robert Martin:
Let’s dive into this diagram for a bit.
There are two popular ways to interpret this diagram: as a cross-section of a sphere, or as a traffic cone/step pyramid.
When looking at it as a sphere, we say that the business domain entities are the inner core, and that the UI, APIs, DBs or external devices are the public shell.
The inner core is called from the use cases, which are called from the controllers, presenters, or gateways, which are called from the public shell. When looking at the system from the outside, you mostly move into the system from the devices and UI and DB, into the controller layers, which then call the use cases, which call into the business domain entities.
When looking at the diagram as a traffic cone, then you have the tip which is the most pure and abstract, having zero dependencies and rarely changing. The wide, bottom layer is then the most concrete and most likely to change. Each layer of the cone has dependencies on the layer above it. Both understandings are useful.
The flow of control in the corner shows how each object within the system has 3 parts. It has the input port, the output port, and the interactor that converts the two. These parts can be interfaces or objects or properties, depending on the complexity, language, and level of abstraction, but all cross-boundary communication happens in these ports. This ensures that the code in any particular layer knows nothing about code in the other layers.
There is a common misconception that this diagram sums up all the main points of the architectural pattern. Using this diagram alone, the naive and simple implementation of this is to create a folder structure matching the high-level layers. In fact, some tutorials online present the following simplistic folder structure as how to implement Clean Architecture.
However, this is a mistake for two reasons:
- A change to the employee onboarding use case will result in changes made across four packages. This means that the code is violating the rule of having high cohesion.
- The architecture does not ‘scream its purpose’.
The intended layout of the packages in clean architecture is what Robert Martin calls ‘Screaming Architecture’. The idea here is that your package structure screams the intent of the app without looking at the individual classes or lines of code.
The more correct structure would look like this, where each business domain is easily identified from the top level:
However, these two images alone aren’t complete either.
If you just work from the first two images, you will likely make the classes tightly coupled, and will often get confused on how to keep the layers separate. This is because there isn’t enough information in these two diagrams to properly understand the role and purpose of ports and adapters between the layers. The internal flow of data between business domains isn’t as clear as it could be. After making the mistakes of tightly coupling the classes, and improperly separating communication across boundaries, many give up on the architecture, considering it too complex and at the same time, not providing enough benefit.
This is because we have a very high and a very low level of abstraction, without the complex glue to bring it all together. There is a third diagram which is needed to complete the picture, and this is the information/data flow diagram.
Robert Martin presents the diagrams as this:
Which we’ve recreated with more concrete examples and clear boundaries from our fictional and anemic ‘Payroll App’ here:
In this diagram, the green arrows represent ‘input, and the red arrows represent output. Notice how the arrows in the first and third diagrams go in opposite directions. From outside the system, data goes in from the view, goes through the controllers and databases into the use cases and heads towards the center of the system, which is the entities.
However, in the third view, all the Entities are off to the side. Entities get populated by the repositories and those entities are injected into all the controllers and use cases that make use of them. Notice how all the red arrows move in one direction. This is known as dependency inversion.
Note that inside a region, inputs (green arrows) and outputs (red arrows) flow in any direction. However, once a boundary is reached, green and red arrows only flow in one direction. Inputs go INTO a region, and outputs come out of a region. It’s very easy and tempting to have the repository be an input to the generatePaySlip UseCase, and just grab the data directly. But instead, we make sure our Entity classes are the only source of data going INTO the UseCase, being their own output.
So to summarize:
- The first image is a very high-level conceptual abstraction of how you should think about the various layers of your application or service. It introduces class concepts like Repository, UseCase, and Presenter.
- The second image is how we organize our files and packages into business domains, so developers know exactly where to go when making a change. The packages make no reference to the layer concepts; those only show up in the individual files.
- The third image is how we architect the communication among the classes and domains. Notice that data freely moves between packages and layers. Yet when you change the code in one package, it has no effect on the code in any of the other packages.
Working with incomplete data often leads to an incomplete understanding. Whenever you talk to others about clean architecture, make sure you are able to provide all three images, to reduce confusion. If you wish to learn more about various architecture techniques and when to use them, join us or follow this blog. You can also feel free to book time with me if you’d like to discuss things further.
Avi Kessner, Software Architect
Avi started as a Flash Animator in 1999, moving into programming as Action Script evolved. He’s worked as a Software Architect at OnceHub since September 2019, where he focuses on Cloud Native Automated workflows and improving team practices. In his free time, he enjoys playing games, Dungeons & Dragons, Brazilian jujitsu, and historical European sword fighting.