[Architecture] Domain Driven Design Through Onion Architecture
Apr. 16, 2021The first time that I came across onion architecture was at my first single-page application at data42. The application employed both the Microservice and Onion architecture for the core domain, which is risk and conflict assessment. After some digging, it seems to me that there’s a lot praises for Onion Architecture. Therefore, I started this post as a beginning of a series that will focus on the Onion architecture.
What is Domain Driven Design
A technique for developing software that focuses on collaboration between technical experts and domain experts. Domain experts can also be called business experts. These are the people who knows about the business rules that you are trying to model. And this could be people like marketin specialists, accounting executives, office managers, etc. They may or may not have any technical knowledge.
When dealing with a domain expert as a technical expert, it is very important that there is a language you can communicate in so that everybody understand what you are talking about. The ideas and terms of the domain should be directly reflected by the code through the Ubiquitous Language. It is basically taking the real world business items and reflecting them directly in code.
Bounded Context
One of the problems when trying to model a domain is that often time the domain can be quite large. You have all the terms, you have all the products to sale to the customer, you have all the maintenance you have to think of. It’s very cumbersome and difficult to deal with. So what we do is that we split our domain into something called bounded contexts. Bounded contexts allow you to sub-divide your large domain into smaller domains, each of which can have it’s own ubiquitous language and it’s own model. Some concepts might be shared between the bounded contexts, but they may look very different from one context to the next and the rules surrounding the concepts can also be very different.
When building using a DDD, there are many different architectures that you can use, for example, the traditional layered architecture.
Traditional Layered Architecture
The layered architecture as its name suggests, has multiple layers. Usually there’s at least 3 layers that are present and each layer may be sub-divided into smaller layers. The three major layers that are genuinely present are the presentation layer, business/domain layer, and then the data access layer. The basic idea is that the user interface is at the top, and the database is at the bottom, with the domain in between.
Onion Architecture
Onion architecture takes a slightly different approach. It usually has 4 layers that can be sub-divided into smaller layers as well. They are infrastrucutre layer, the API layer, the domain layer, and the core layer. Each layer can see the underlying layer but the inner layer has no visibility or knowledge of the outer layer.
The Core
When building with the onion architecture, we start off with the centerpiece: the core layer. The core is the building block you use to build your application. It is not specific to any domain or technology. It includes things like Lists, Maps, Classes, Actors, Lenses.
The core will never include technological concept like REST, SQL, or database. The core also has no knowledge of the other layer.
The API
The API acts as an entry point to the Domain and it should use domain terms and domain object to communicate. It should also be restricted to exposing only immutable objects. The reason for this is to prevent people from using the API to gain access to a backdoor to the domain. If you return mutable objects to your API, the people using that code could have access to parts of the domain that you did not intend to expose.
The API has access to the Core and the Domain but not the Infrastructure.
The Domain
The classes and methods in the domain should be or must be named according to the Ubiquitous Language. The key thing is in the domain is where all your business logic goes. If you have any business rules, they belong here. The reason is that by controlling your domain through the API and by putting all the business logic into the domain, it makes your application portable. You can extract any of the technology bits without losing any of your business logic.
Avoid primitives in domain (not required). Container class, for example, to provide validation logic and provide strong typing on those fields.
A few building blocks: value objects, entities, aggregate roots, repositories, factories, services
- Value objects: immutable, used for message passing, and useful in the API to expose your domain concept without exposing the mutable aspects
- Entities: potentially mutable, identifiable through an id rather than an attribute, the state of the entity may change but if they have the same type and id, they are considered the same regardless of what attributes they have
- Aggregate roots: entity that binds together other entities, external object does not allowed to have a reference to a child of an aggregate root entity, access must go through the aggregate root
- Repositories: abstract away a lot of the storage concern that can pollute the domain, could be file based, database, memory or REST API or comnbination of those. Should not be confused with data store, its job is to store aggregate roots. Underneath that repositories implementation may actually have to talk to multiple different storage location in order to construct the aggregate roots. Implemented as an interface with the logic in the Infrastructure
- Factories: abstract away new object construction
- Services: provide home for operations that don’t quite fit into an aggregate roots
The Infrastructure
The outermost layer of the onion architecture. It includes adapters for various things like databases, user interfaces, and external sercives. It will have full access to the API, the domain, and the core, although generally speaking, all access to the domain should go through the API.
(To be continued …)