The Practical Onion Architecture — the onion that doesn’t make you cry
Hi, folks in my new job I’m the owner of implementing a new architecture, so, I was presenting a new architecture, separating the responsibilities between the domains, and services. Also, I propose a new code design based on the Onion Architecture, and I’ll show you in this medium story.
The Onion Architecture
The onion architecture was an architecture proposed by Jeffrey Palermo
The architecture has the purpose that to protect the use case from external interactions. In other words, this approach avoids coupled with external layers, through dependency injection.
Alistair Cockburn has written a bit about Hexagonal 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.
Motivation to use
There are many projects that put the business rule in controller, view, also in data access layers, and sometimes queries with business rules that access the database, creating a strong coupling between the layers, sometimes getting harder the reuse of the classes generating code duplications, getting harder unit tests and others problems.
Hands-on
This hands-on is in Java language with SpringBoot framework but feel free to choose the language and framework that you love.
I want to do this hands-on closer to a real project, so we need context.
Context
The implementation is based on a project in which store cryptocurrencies’ purchase orders
Architecture Overview
IMG — 03 Image based on the Onion Architecture for our project
Firstly generate the modules and project on SpringIO Website, and configure the project as the image below.
Layers such as modules
Main pom.xml
Each maven module represents the layers that were presented in IMG — 03
But you could organize your project using folders. I believe that the layers can be represented as modules, it is easier to maintain, and also more scalable and extensible
Business Layer
The business layer is the system’s core, in charge of keeping the business rules and domain objects.
Use Case: Purchase Order
So, let’s create a new use case, that is in charge to buy a coin. Before buying a coin it’s necessary to check the current value of the coin requested. So we need to request this current value to an external service, in which we gonna use the CoinMarketCap
Knowing these requirements, we can implement the use case with a focus on business rules.
Business Module
In the business module there are the interfaces and classes:
PurchaseOrder — is the domain object of the business layer. Represents a coin purchase order
CreatePurchaseOrder —it’s the use case itself implemented
PurcaseOrderRepository — Interface that communicates with the data-provider layer
CoinIntegration — Interface that communicates with the third-party-service layer
With this approach, we can see that not been necessary yet, to know how will be the implementation of the interfaces.
So, we can implement the use case, just focusing on the business rules, no need to worry, about how will be the database model, or how will be integrated with external services at this moment.
Also, this approach makes it easier, to create unit tests.
Let’s create a unit test for our use case
Now, let’s create the implementation
In this example, we can see that my Business Module doesn't have a framework coupled.
Someday Martin Fowler’s Twitter said:
A chosen framework is stuff that is hard to change. My motivation that not adding spring dependency for the business module is just to show that is possible that the business layer to be without any frameworks.
Data Provider Layer
This layer is in charge of communicating with the store, which saves the purchase order.
There is in this layer the implementation of the PurchaseOrderRepository, so, that layer depends on the business layer and other libs/frameworks that will use for implementation.
pom.xml — data-provider module
Data Provider module — v1
PurchaseOrderRepository — Interface that gonna create the communication between the business and data-provider layers by DI — Dependency Injection.
PurchaseOrderEntity — Represents the database model
PurchaseOrderRepositoryInMemory — implements the interface PurchaseOrderRepository from the business layer, this implementation gonna saves the data in memory.
PurchaseOrderConverter — Convert the model object to the database model
IMG-04 — PurchaseOrderRepositoryInMemory implements the PurchaseOrderRepository
Let’s create a new Integration Test to check if everything is ok with our implementation.
We can see that the business and database layers don’t are coupled … But the implementation of the PurchaseOrderRepositoryInMemory is in charge of for decouple AND saving the purchase’s data.
In this case, the implementation isn’t extensible.
So, let’s improve this
IMG-05 Data Provider module — v1
PurchaseOrderRepositoryImpl — Now, there is an implementation that receives by dependency injection, another interface that is in charge to be implemented
PurchaseOrderRepositoryEntity — Interface that gonna receive the implementation of the database communication.
PurchaseOrderEntity — Represents the database model
PurchaseOrderRepositoryInMemory — implements the interface PurchaseOrderRepositoryEntity, this implementation gonna save the data in memory.
PurchaseOrderRepositorySpringData— implements the interface PurchaseOrderRepositoryEntity, this implementation gonna save the data in a physic database managed by SpringDataJPA
PurchaseOrderConverter — Convert the model object to the database model
Let’s create a new Integration Test to check the integration with our database, in this case, I’m using PostgreSQL.
Let’s check the implementation that has been improvement
PurchaseOrderRepositoryImpl receives the implementation purchaseOrderRepositoryInMemory but could be purchaseOrderRepositorySpringData
PurchaseOrderRepositoryEntitySpringData implements PurchaseOrderRepositoryEntity saving the purchase orders in memory
PurchaseOrderRepositoryEntitySpringData implements PurchaseOrderRepositoryEntity using the SpringData JPA saving the purchase orders in a Postgres database.
Application Layer
Is where startup the application there are all endpoints, entry points, and also integration tests.
This layer depends on the business and data-provider layer, and also contains the frameworks that will help us to implement the endpoints.
PurchaseOrderController — Receives the HTTP requests and invokes the use case CreatePurchaseOrder by DI.
PurchaseOrderRequest — This is a DTO — Data Transfer Object, that is in charge read a payload that sends in the body requests.
PurchaseOrderResponse— This is a DTO — Data Transfer Object, that is in charge to show the request results.
PurchaseOrderControllerConverter — Convert the model object to the DTO Request and Response.
Firstly let’s create a contract test, that gonna HTTP POST to save a new purchase order
Our test is done, now let’s check how are the implementation
PurchaseOrderController — Receive the POST HTTP
We can see that the CoinIntegration has not been implemented yet, só probably that this implementation is failed yet. So let´s implement the third-party-service layer.
Third-Party Layer
This layer is in charge of communicating with external services, in our case, we check the current price of the coin that we chose.
We can request the current price of the coin, in the CoinMarketCap API.
Third-Party Layer
This layer is in charge of communicating with external services, in our case, we check the current price of the coin that we chose.
We can request the current price of the coin, in the CoinMarketCap API.
Third-Party Module
Let´s see how are this implementation
CoinIntegrationImpl implements the CoinIntegration and receives an adapter that implements a client.
So, basically, to get the current price of any coin it needs to fetch by the symbol.
CoinMarketCapFeignClient — Spring Feign Client
View full communication between the layers
Check the full code in my GitHub repo
https://github.com/andrelucasti/start-project-onion-arch
Conclusion
The onion architecture is a form to build software with a focus on business rules, isn’t necessary to start with databases or APIs.
If you need to build an application with “longevity” that needs scalable, this design is a good way.
If you are developing an application that is a simple CRUD that doesn’t need scalable, I think this design is “too much”, my suggestion is that you use the TDD approach covering the use cases using integration tests.
References:
Jeffrey Palermo — https://jeffreypalermo.com/tag/onion-architecture/
Robert C. Martin — Clean Architecture
Herberto Graça — @hgraca