This post is about Microservices and Domain Driven Design (DDD), how to apply DDD to Microservices architectural style, what are the pitfalls to be aware of and many other aspects. If you need a quick refresher or intro to DDD, then you may refer to Simple Domain-Driven Design – Building Blocks. Here is a series of posts about Microservices that elaborate more on the topic.
What’s the subject?
When we talk about applying DDD to microservices, what do we exactly mean? What is challenging in applying DDD to Microservices? The answer lays in the Microservices properties, where each microservice is:
- Independent and complete unit of functionality
- Independent unit of development
- Independent unit of deployment
- Independent unit of scale
- Does not directly expose data storage
- Provides APIs to access its functionality
While in DDD we have to deal with different levels of granularity:
- Entity and Value Object
- Aggregate Root
- Bounded Context
Having Microservice as independent and self-contained matter on one side and different levels of granularity in DDD on the other side raises a question. On what DDD granularity level do we build Microservices? Should we build a microservice per bounded context? Or may be per Entity, or somewhere in the middle? Obviously not a microservice per application or else we end up with SOA or Monolith architecture style.
The main problem we are going to solve in this post is to decide what is the right granularity for microservices.
What’s the right granularity?
The short answer is “It depends…”. Yes, there is no definite answer, however we can still figure thing out. Let’s look closely at each level of granularity and find advantages and disadvantage of creating microservices at that level.
This level signifies entire application. In case when entire application domain model fits nicely into a single service or into a monolith, then maybe we don’t need to design system with microservices architectural style. With application growing we may decide to migrate to microservices, however doing so prematurely may turn into a huge problem. It is hard to predict how our domain model is going to evolve in the future, therefore prematurely splitting into microservices may create a lot of problems with further refactoring. Moving domain objects between microservices is not as easy as withing a single service or a monolith.
Bounded context may be a good candidate for a microservice, it is naturally isolated from other bounded contexts, each is based on its own model and ubiquitous language dialect. Bounded contexts communicate through interfaces, APIs, isolation layers and etc. and have clear boundaries. However, when bounded context is large it is unreasonable or even impossible to use it as a basis for a single microservice. There are teams that own a single domain model, hence a single bounded context. Under any circumstances these teams would be able to pack entire model in a microservice. So bounded context can be used as a boundary for a microservice, but only when the model is small so that the microservice is indeed micro.
The short answer is aggregate root may be the best option to build a microservice around. By definition aggregate root encapsulates business logic and rules, all complexities associated with storage and retrievals of entities and value objects. It is responsible for maintenance of model’s invariant and exposes its functionality to outside world through an interface. All of this resembles pretty well with what microservices are about.
The technique of creating a microservice from an aggregate root is usually simple and straightforward. Aggregate root already exposes an interface, so we can take the interface and expose it through microservice APIs.
Entity and Value Object
Value objects are domain objects with no identity. It’s hard to image a case when it’s reasonable to have a microservice around a value object, so there is no need to discuss it further.
Entity is a domain object that has identity, even though the identity is valid withing an aggregate root it’s still possible to build a microservice around entity exposing CRUD operations, however it doesn’t mean that we should. In most of cases microservice per entity becomes an overkill and negatively impacts reliability and performance of entire system. How?
Well, microservices communicate over network and network call is neither as much reliable nor as fast as in-process call. Excessively fine grained microservices are tend to increase amount of network communication making entire system slower and less reliable.
Another disadvantage is increased complexity and maintenance cost. Having a lot of tiny microservices makes it extremely difficult to comprehend the system as a whole and navigate through dependencies. Entire system becomes more looking like a monolith, or even worth, distributed monolith.
Increased coupling is another downside. With microservice per entity it’s quite probable to end up with cohesive entities being split apart into different microservices. Increased coupling results in need for orchestrated deployments of coupled microservices, which is quite opposite to one of the main advantages of microservices architectural style – independent deployments of each microservice.
So… at what DDD granularity level we can create microservices? Practically speaking the best option to try is at Aggregate Root level. In few cases Bounded Context is a reasonable option too, however Entity is rarely a good choice. When making a decision it is not always possible to go strictly by Aggregate Root. Sometimes more than one Aggregate Root may become a microservice, and that is OK, as long as we maintain properties of microservices.
Think of drawing microservice boundary somewhere between Bounded Context and Aggregate Root.
When we make design choices, we need to be consciously aware about our goal. Any design decision is a tradeoff to minimize complexity. Why? Complex systems are expensive to maintain, take significant time to introduce new features and risky to make changes. That is basic idea behind any architecture and design decisions, including where to draw microservice boundary.
|Domain-Driven Design: Tackling Complexity in the Heart of Software, by Eric Evans. Great book to start with DDD. Explains complicated concepts in easy to understand fashion with real-life examples.
|Implementing Domain-Driven Design, by Vaughn Vernon. Goes deeper into DDD and focuses on applying DDD while designing and implementing software.
|Monolith to Microservices: Evolutionary Patterns to Transform Your Monolith, by Sam Newman.
|Microservices Patterns: With examples in Java, by Chris Richardson.