It helps to think of this as an update to Ports & Adapters that brings more fine-grained control over the Application Core.
Bring back the Layers
When we talked about Ports & Adapters we mentioned that it sort of got rid of the layers. More specifically, it left us with just two of them.
We only had an external layer (with our Adapters and the relevant infrastructure), and an internal one (with pretty much everything else).
As you might imagine, especially in enterprise applications, having most of our code bundled in a single layer can be… Troublesome.
That’s exactly what our Onion is here to solve. To keep it simple: It’s a more detailed specification regarding how to organize what remains of our code after defining where the boundaries (Ports & Adapters) are.
Let’s have a look at our new layers, from the inside-out.
Domain Model
Our core business Entities, enriched with their corresponding rules and logic (once again, anemic Entities are not welcome).
Anything specific to our Domain and/or business logic that does not require Entities to interact with one another goes here.
Hopefully, this piece of our code will change only if our most basic business rules change.
Domain Services
Similar to how we reasoned about Interactors when talking about EBI, all Domain logic involving multiple Entities will go here.
So again, whatever business and\or Domain logic doesn’t fit the scope of one single Entity (or value object for that matter) belongs here.
Application Services
The use case orchestration layer.
It has two distinct responsibilities:
- Orchestrate the required Domain Services and/or Models to fulfill the use cases of the application.
- Present interfaces for the infrastructure to implement
More or less in line with Ports & Adapters, here we’ll find use cases as well as ports (or interfaces for the infrastructure).
Inwards dependency
As you can see, this architecture will enforce the same ‘direction of dependency’ as Ports & Adapters, only this time it applies to our own code as well.
It’s not just that our infrastructure depends on our Domain and not the other way around. Now also the layers within our domain depend on whatever layers lay beneath them (not necessarily only its direct inner layer).
This way, we end up with an independent core that can (and should) be compiled, executed and tested with no specific infrastructure.
This architecture forces us to couple towards the center.