As I’ve described in earlier posts I like to use a Hexagonal (or Onion) architecture when building enterprise back-end systems. The reason for that is that it places the domain model in the center and surrounds it with whatever integration code is needed to make it work in the technical environment where the system is deployed. It also makes the domain model totally independent of anything outside itself, which makes it super easy to thoroughly test drive without any execution environment.
In a recent discussion (as in so many others) we pretty soon got down to the question why we need all the layers. The simple reason for that is that every layer has its distinct reason to exist, so I figured the best way to motivate them is to describe those reasons on a per layer basis. So here it goes.
This is the center, the heart, of the system. The sub-title of Eric Evans’ wonderful book is “Tackling complexity in the heart of software” and that is what Domain Driven Design is all about. The book is full of good advice on how to do that, but from a layering perspective I think the number one technique is to separate the inherent complexity of the domain (i.e. the complexity of the business) from the accidental complexity that we introduce by using computers to solve the problem of the business (i.e. the complexity of using a database, messaging service or deploying as a web application). In short: do not mix code implementing business logic with code handling technical matters! Hence, the domain layer is where all the business logic, and nothing but the business logic, goes. Any service needed by the domain, e.g. a repository for storing entities, are defined as an interface, in business terms.
Infrastructure (part of the integration layer)
Surrounding the domain layer we have the code responsible for integrating to the “outside world”, the “anti-corruption layer” in Evans’ terms. I commonly call this layer “integration”, but it falls into different parts. One is the “infrastructure” or “persistence” and another is “application”.
“Infrastructure” or “persistence” commonly is about integrating with a database or messaging infrastructure but it could also be about integration with other systems. In short, this is the layer where any services defined as interfaces in the “domain” get their technical implementation. In this layer there should be nothing but technical concerns, and we must be careful not to let any business logic leak out to this layer.
Application (part of the integration layer)
This is the part of the integration-layer where requests to the domain model come in from the “outside world”. Just like integrating with a database or another system is a matter of converting between different models (the purpose of the “anti-corruption layer” described by Evans) this part of the integration layer is also solely about conversion between the model of the incoming request and the domain model. This conversion can be split into three parts:
- Data conversion: Incoming data are converted to their counterparts in the domain model and later back to the data format of the return value.
- Functional conversion: The incoming service call is “converted” into one or several calls to the domain model. This often includes finding an entity from a repository, calling a business method on it and storing it back in the repository. But it could also translate into a single call to a domain service.
- Error handling: Internally the domain model most likely uses unchecked exceptions with specific meaning to the business. In the “outside world” other error handling mechanisms might be in use that requires conversion.
Other than that the application services also tends to be responsible for starting and committing transactions.
In this post I’ve tried to show the very different responsibilities of each layer (application, domain and infrastructure) in my typical back-end systems architecture. This is an approach I’ve been using successfully for several years in a non-trivial system and although it has many properties similar to Hexagonal architecture, it doesn’t adhere to it in every bit. The reason for that is not in any way scientific, it is purely because I didn’t know about it at the time where I set out to design the architecture of the system I was working on to support the use of DDD (and then first and foremost separation of business and technical concerns) in the best possible way. Therefore the basis of the architecture described here, is the concepts of a Domain Model in a Bounded Context surrounded by an Anti-Corruption Layer as described by Eric Evans in his book.