The Onion Architecture 🧅
After the Hexagonal Architecture article, now it’s time to talk about Onion Architecture.
You can check the first article of this series here about Hexagonal architectures
What is the Onion Architecture?
So, this architecture pattern was promoted by Jeffrey Palermo in 2008 trying to solve common problems when maintaining applications and also emphasizing the separation of concerns throughout the system. It was presented as an alternative to the well known Tradition Architecture:
The problems with this kind of architecture are all over the place but as Jeffrey Palermo says it starts wrong when each subsequent layer depends on the layers beneath it, and then every layer normally will depend on some common infrastructure and utility services.
That problem would be represented in something like this:
So what Jeffrey Palermo proposed is a new approach to architecture.
Honestly, it’s not completely new, but I’m proposing it as a named, architectural pattern. Patterns are useful because it gives software professionals a common vocabulary with which to communicate. There are a lot of aspects to the Onion Architecture, and if we have a common term to describe this approach, we can communicate more effectively.
The main premise of this architecture is that it controls coupling. The fundamental rule is that all code can depend on layers more central, but code cannot depend on layers further out from the core. In other words, all coupling is toward the center.
So imagine the prior diagram example where Foo and Bar classes were coupled to each other even they are living in different layers. Following that main premise, you would need to control coupling in a similar way to this:
Remember that main rule when talking about Onion Architecture.
Benefits of Onion Architecture
- Sustainability / Timelessness: by decoupling our application-business code from the tools we are using (i.e. the libraries and frameworks), we make it less vulnerable to the erosion of time and IT fads
- Testability: We are decoupling everything between different layers, so everything can be tested.
- Adaptability / Time to market: adding a new way to interact with your application is very easy.
- Understandability: Rather than having a solution where use cases are completely lost or mixed within all the technical stuff, this architecture style states the emergence of an applicative-use-case-layer
- Use case driven: Indeed, with this architecture style, we design our applications with our use cases in mind; not the number of persistence technologies or binding types we will need to support
Disadvantages of Onion Architecture
- Interface soup: lots of interfaces for interaction with the core which can make navigating a project tougher.
- Core heavy: lots of logic and movement in the core, any changes made inside the core could affect behavior outwards. But if this didn’t happen it would be a miracle.
Let’s take a look now at the layers of our onions 🧅🧅🧅
Layers of the Onion Architecture following DDD
In the very center, we see the Domain Model, which represents the state and behavior combination that models truth for the organization. Around the Domain Model, there are other layers with more behavior. The number of layers in the application core will vary, but remember that the Domain Model is the very center, and since all coupling is toward the center, the Domain Model is only coupled to itself.
The first layer around the Domain Model is typically where we would find interfaces that provide object saving and retrieving behavior, called Repository Interfaces.
If you are thinking about DDD after reading that you are right. Repository Pattern belongs to Domain Driven Design and can be described as:
Pattern that mediates between the domain (aggregate roots) and data mapping layers using a collection-like interface for accessing domain objects.
You may think where the implementation of that Repository Interface may reside. Jeffrey Palermo mentioned that out on the edges we see UI, Infrastructure, and Tests. The outer layer is reserved for things that change often. These things should be intentionally isolated from the application core.
So our implementation of that repository interface should live in the infrastructure layer, isolating everything from the core. If one day we need to use MongoDB instead of MySQL when accessing data we don’t need to worry about the application core or business logic. We only need to focus our effort on the implementations of the Infrastructure layer. Because those as far as you are not changing anything about in the business rules, you don’t need to modify the Repository Interfaces or the Domain Model.
A couple of pictures are worth than thousand words. So here you can find the places where the Repository Interfaces and Implementations reside in the Onion Diagram and as well in the UML diagram.
At this point, someone may be asking if the very center is the Domain Model or the Database.
I just want to emphasize that the database is external. Some people are used to think applications where the center is a database running triggers, stored procedures or jobs. If you follow this Onion Architecture there are no possible database applications. Most of the applications might use a database as a storage service but only though some external infrastructure code that implements an interface that makes sense to the application core. Decoupling the application from the database, file system, etc, lowers the cost of maintenance for the life of the application.
So… where the hell is my business logic?
Let’s get this straight.
Around the Domain Model, we can see two layers. Domain Services and Application Services.
- Application Services: Typically used to orchestrate how the outside world interacts with your application. For example,
AuthenticationServicewould be an Application Service that co-ordinates how a user should be authenticated. In other words, they are like the use cases of our application.
- Domain Services: When a significant process or transformation in the domain is not a natural responsibility of an ENTITY or VALUE OBJECT, add an operation to the model as a standalone interface declared as a SERVICE. Define the interface in terms of the language of the model and make sure the operation name is part of the UBIQUITOUS LANGUAGE. Make the SERVICE stateless. For example, you might have a
RegisterUserServicethat co-ordinates how a user is registered within your application.
To explain the difference between these two concepts and understand when and how to use them I am going to take an example from this great repository about DDD: https://github.com/zkavtaskin/Domain-Driven-Design-Example
This code created by Zan Kavtaskin ( http://www.zankavtaskin.com/) is a great example of how to apply DDD within an Onion Architecture.
The entry point of the application is an API (Presentation layer), and after that reaches an application service to go deeper to the center, the domain model, like following below diagram (just imagine a Presentation Layer outside the Application Layer)
- So, let’s take a look at the CartController.cs
2. When the API reaches this route in this controller we are using the CartService which allows users to interact with the Cart in this e-commerce application (Application Service living in the Application Layer)
Here is the code for CartService.cs Application Service:
In that CheckOut function, it is easy to see how this Application Service is being used to interact with external users and coordinate the use cases of our business rules (business logic). Like trying to verify if a customer has already created a cart and verifying that that cart is linked to the same customer, if they can do a checkout or not and whether they can create a purchase or not.
3. And in some cases, that CheckOut function is making use of the CheckOut Domain Service (checkOutDomainService) to add the checkout operation to the model and create a Purchase. Let’s take a look at this specific Domain Service:
First of all, let’s review the definition of Domain Service again:
When a significant process or transformation in the domain is not a natural responsibility of an ENTITY or VALUE OBJECT
We can understand how this is being used in the application to follow DDD. This Domain Service is a process (checkout) which is not the responsibility of the domain model itself and it is adding an operation to the model (Purchase Aggregate Root).
In other words, we are handling the domain logic here.
And that’s how you manage the business logic and the domain logic using Application Services and Domain Services in Onion Architectures and DDD.
If it’s the first time you are reading something like this, I know how you feel right now
But everything will be in place once you just try to use these concepts in your code. The practice is always the key to master 🔨🔨🔨
Let’s get a summary of what was covered until this point (I know it’s a heavy and long article):
- What is the Onion Architecture?
- Domain Services and Application Services
So there is only one thing left, and that is….
Onion Architectures vs Hexagonal Architectures
I have seen a lot of people talking about this like a new Marvel movie, they tell you to choose, Onion team or Hexagonal team.
I try to be a guy that does not see everything in black and white. I like to learn, explore and understand but always taking into account the benefits of any new trend, principle or whatever. That’s the reason why I am not going to explain software architectures as a war, trying to convince you which one is the best and why you should use it alone. I am not going to do that.
Instead, I am going to focus this on how to use different architectures in a combined way, trying to show the benefits of it and when I would use them.
Remember to read Part 1 of this series where I collected a lot of information about Hexagonal (Ports and Adapters) Architecture
To summarize Hexagonal Architecture, let’s bring back quickly a self-explanatory diagram:
- The Hexagon — the application
- Driver Port — API offered by the application
- Driven Ports — required by the application
- Actors — environment devices that interact with the application
- Drivers — application users (either humans or hardware/software devices)
- Driven Actors — provide services required by the application
- Adapters — to adapt specific technology to the application
- Driver Adapters — use the drivers’ ports
- Driven Adapters — implement the driven ports
So, Hexagonal Architecture talks about Primary and Secondary Actors, Ports, Adapters and the Application. As I mentioned in Part 1, Alistair Cockburn didn’t mention anything about how you should do your code. Let’s quote this again:
The Ports and Adapters original article doesn’t mention anything at all and how code must be structured inside the Hexagon/Application ( http://web.archive.org/web/20180422210157/http://alistair.cockburn.us/Hexagonal+Architecture). I have seen a lot of articles talking about DDD or application layers but Alistair Cockburn didn’t write on how you should do your code, he only promoted the idea of ports and adapters
In other words, we can architect our application code with anything we want to. And given that this is the Onion Architecture + DDD article this is how I want to draw my architecture diagram at this point:
We now have ports, adapters and the Onion Architecture. And the key tenets are clear as well:
- The application is built around an independent object model.
- Inner layers define interfaces. Outer layers implement interfaces.
- The direction of coupling is toward the center.
- All application core code can be compiled and run separately from infrastructure.
Also, Hexagonal and Onion look for the same goal as Jeffrey Palermo mentioned in his first article about Onion Architecture:
Hexagonal architecture and Onion Architecture share the following premise: Externalize infrastructure and write adapter code so that the infrastructure does not become tightly coupled.
And I like to see it in this way. In tons of StackOverflow answers, tech blogs or even GitHub code samples a lot of people try to stick to one of these architecture patterns without having in mind that these patterns can work together.
And personally I would use them together for every application I need to be scalable, maintainable, highly covered by tests, evolvable and understandable.
Here some repositories with great examples of what I mentioned in this short article:
In the next article, we are going to see how this Hexagonal+Onion Architecture works with the Clean Architecture pattern promoted by Uncle Bob.
If you have read all of this, thank you very much. Feel free to connect with me on Linkedin!