(Viable) Domain-Driven Design

Joey Feldberg - Jul 16 - - Dev Community

Domain-Driven Design is tool that will help you build better software, but it might also just be a waste of your time.


When Should You Use Domain-Driven Design?

If you’re building a new billing system, warehouse management solution, or streaming service, it’s crucial to step back and first understand and model the problem you’re trying to solve. These are complex domains with significant business semantics that require careful consideration.

There are also scenarios where you’re dealing with a smaller domain or only a part of a domain because you lack the time to explore the entire domain. Domain-Driven Design is suitable for both situations. However, for complex domains, I recommend investing the time to delve deeper and maybe even read a book about Domain-Driven Design.

For most other solutions, such as simple services or even complex domains that don’t require a comprehensive understanding, it might be more beneficial to invest your time elsewhere.

What is “viable” Domain-Driven Design?

A Domain-Driven Design, in short, should follow these basic rules:

  1. Capture the domain model, in domain terms, through interactions with domain experts. Talk to the business owners and the product expert, only with their cooperation you can form the domain model and a shared domain terminology1, it should span the entire process, from the product concept, how it's implemented and how it's presented to the users.

  2. Embed the domain terminology in the code. The code is the domain model, and it should reflect the domain, the names of constants, classes, events, etc.

  3. Protect the domain knowledge from corruption by other domains, technical subdomains, etc. No model is perfect; it must evolve and repair itself over time. Avoid taking significant shortcuts, and keep context bounds separated.

What is a "domain"? It's essentially the business, it encompasses all the knowledge in a particular system and its business logic, it is the problem that we’re trying to solve.

What is a "domain model"? It's a representation of the business, but it also serves the implementation of the system, it is anything that allows both domain experts and developers to discuss the domain and its implementation, it’s the solution to the problem.

What is the "domain terminology"? To facilitate knowledge sharing between the business owners and the developers, we need a shared language. For this, we use the domain's language, business terms.

The code, diagrams, or any other document should also speak in the same language, when any model language changes, code changes.

What is a "bounded context"? Some domains are complex and may be naturally divided into smaller subdomains. Each subdomain is separated by a bounded context, meaning the language used from one bounded context is different from another and they do not share data directly.

For example, you might use the term "account" for many things, from the user account in your identity system to a billing account in your billing system, you can now see that a system is broken down to several domains, in each group we might have several subdomains, each subdomain could be a bounded context.

Are microservices bounded contexts? No, microservices is about architecture while bounded contexts are all about business subdomains, of course, it might just be sometimes that a microservice covers a domain, but in general, a microservice should not span more than one bounded context.

What's inside a bounded context?

Diagram illustrating the structure of an Aggregate in Domain-Driven Design (DDD). The Aggregate contains an Aggregate Root at the top, which branches into an Entity and a Value Object. The Entity further branches into a nested Value Object. The Aggregate is enclosed in a dashed blue border. To the right of the Aggregate, a dashed line connects to a Domain Event, indicating its relation to the Aggregate.

Value objects

A value is most of the time something that is used to describe an entity, it is just a value, like a name or a phone number, it should be immutable.

Entities

An entity is a unique thing that has an ID, it is an individual thing, and its state might change over time, it may reference other entities or value objects.

Aggregates

Aggregates are the key concept of a model, each bounded context might have many aggregates, where each aggregate has entities and exactly one entity that is called the aggregate root.

Aggregates are consistent, they should be considered as a single transactional unit, they are candidates for microservices, for example an order has products and products have prices, but still, we treat an order as a single unit when writing or reading from the database, we must ensure consistency within these operations.

Domain events

The aggregate notifies other parts of the system when something happened.

Example

We are building a new billing system called ‘bling’. There are many aspects of billing customers, customers add items to purchase, and the system creates a bill for it, we also must handle payments, what about recurring billing like subscriptions, creating invoices, etc.

To apply Domain-Driven Design we must understand the entire business and all the possible connections between different business activities.

There is no right way to do this, but you do need the right mix of people, this includes domain experts, developers, UX, or any person who has a key interest in the domain or needs to understand the business better.

One emerging method of distilling all the domain complexities is a method called event storming, which is not explained here, but it can be a good way to make sense of the business complexities when it really is very complex.

We’ll start to define the different use cases our billing system does; after talking to all the domain experts we know that:

  • Customers can order products.

  • Customers can subscribe to subscriptions.

  • Customers can cancel subscriptions.

  • When an order completes the customer receives an invoice.

  • On every subscription billing cycle, the customer receives an invoice.

  • Customers can see their orders/subscriptions/invoices

  • Orders or subscriptions could be paid or free.

  • On every subscription billing cycle, the customer pays accordingly.

  • We could have multiple payment options.

  • Customers should have an account.

  • Accounts have permissions.

After discussion with the domain experts and developers we decide that payments have its own complexity that is not really part of a billing system, also accounts and roles are not really in the same language of a billing system.

This is a rough possible domain of a billing system, with two ancillary domains that our core domain of billing needs, these domains are our bounded context.

Customer, account and buyer are the same thing, but they each have different meaning in different contexts.

Diagram showing three bounded contexts in a Domain-Driven Design framework. The ‘Billing’ context, highlighted in pink, includes Customer, Order, Subscription, Product, and Invoice. The ‘Identity’ context, in blue, includes Account, Role, and User. The ‘Payments’ context, in teal, includes Buyer, Payment, and Payment Gateway. Each concept is represented as a sticky note within its respective context.

Focusing on the billing domain and its use cases we defined above, we can see that we will have customers, orders, products, subscriptions, invoices and billing cycles, these are all entities, aggregates or value objects. Billing cycle is a value object, we would have many value objects, for example, the customers billing address and name. Customer, order, Subscription, product and invoice are entities

Diagram illustrating the relationships between entities within a bounded context. At the top, 'Customer' is connected with arrows pointing to 'Order' and 'Subscription'. 'Order' further connects to 'Product' and 'Invoice' with arrows. 'Subscription' is also linked to 'Order' with a bidirectional arrow. Each entity is represented by an oval shape, with arrows indicating their relationships.<br>

The entities' relationships are roughly depicted above, but we also want to isolate our aggregate. Following the idea that aggregates are transactional boundaries, we decide that customer, order, subscription and invoice are aggregate roots, this concludes that product is an entity inside the aggregate of order.

Upon the completion of an order, we would like to produce an invoice, a domain event called OrderCompleted will notify the invoice aggregate, and on every recurring subscription cycle we would raise a domain event called subscriptionRenewCycle

Drawing roughly the aggregates (with value objects) can look something like this:

Diagram depicting three bounded contexts in a system: Billing, Identity, and Payments. Each context contains related entities represented by labeled boxes. The Billing context (highlighted in pink) includes Customer, Order, Subscription, Product, and Invoice. The Identity context (highlighted in blue) includes Account, Role, and User. The Payments context (highlighted in green) includes Buyer, Payment, and Payment Gateway. Each entity is represented as a separate box within its respective context.

Identify domain-model boundaries for each microservice

Domain-Driven Design fits very well with object-oriented architecture2, every entity is a class, a value object is a class value and every aggregate is a module (or something similar), it leverages a very pragmatic way of arranging code that puts the business into focus.

Domain-Driven Design is also a great way to design microservices. The hardest question when considering microservices is what the optimal size of a microservice is, or what it should contain. There are a lot of vague answers to this question like Fowler’s “2 pizzas rule”, but honestly these don’t tell you anything about what it should contain, Domain-Driven Design can help define clear microservices boundaries.

There are basic rules you can follow:

  1. Microservices should not cross boundaries, this means that a microservice is fully contained within one bounded context.

  2. Aggregates are isolation units, similarly, microservices are isolated units with high cohesion, generally an aggregate is a great candidate for a microservice.

Of course, a microservice could combine several aggregates or an aggregate could be decomposed into more than one microservice, this will require several considerations like scalability or even team sizes.

Sometimes there are services which don’t serve the business directly but are purely technical like orchestrators of several other microservices.

That’s it?

Not really, but it’s a good start. From my experience, when developers start using this or other simple introductions, it greatly improves their design skills, but also gives them a desire to learn more, and for this I will list what I think are the best resources to learn from.

  1. Domain-Driven Design Distilled - Vaughn Vernon
    I consider this to be one of the best practical book about Domain-Driven Design.

  2. Implementing Domain-Driven Design - Vaughn Vernon
    After reading Domain-Driven Design Distilled I found that this gave me more better understanding using real examples.

  3. Domain-Driven Design - Eric Evans
    It was the first book I read, and it’s by the original author so it has to be mentioned, but it was too theoretical for my taste and hard to follow.

  4. https://github.com/heynickc/awesome-ddd - Everything can be found here, maybe even better simplified introductions to Domain-Driven Design 🙂


  1. Also called the ubiquitous language 

  2. But there are good mental models for functional programming that I won’t cover here. 

.
Terabox Video Player