Domain Driven Design Pattern

Chandima Jayamina
9 min readMay 2, 2024

Domain driven design is a way of looking at software from top down. When we develop a software our focus should not be primarily on technology. It should be primarily on business or whatever activity we are trying to assist with the software domain.

In here domain refers the subject area to which the software system applies such as aviation domain, bank domain, e-commerce domain etc.

This is the basic structure of the software, if we are on web this acts as service

Software development is difficult enough without the business and Engineering using different names for the same thing. This is where DDD comes in which was made popular by Eric Evans in his 2003 book.

Strategic Design

The first step is what we call strategic design. Even though it’s possible to use DDD in an existing application it’s much easier when the application is built with it in mind. So we need to work out what the different subdomains are in the business. A domain in this case refers to the subject area in which we’re building the application and subdomains are a part of that application.

| Ubiquitous Language

It’s really important when you’re working out the subdomains that everyone is using the same language. You want the words that you use to describe objects in your application to be the same ones that are used by the business. This has the grand name of ubiquitous language which is really just a fancy way of saying everyone needs to use the same words.

Ubiquitous Language

So let’s use Apple TV as an example. If we were to build Apple TV from scratch using DDD what subdomains would we have? So obviously we have video streaming which is one of the main parts of the business and therefore this is what we call a core domain. And then we might have recommendations as another subdomain and maybe billing which handles all of the subscriptions. There can be few other domains but these can be the main sub domains.

Now working out what the subdomains are should always be done as a group exercise with the business. If the engineering team tries to work out what all of the subdomains are themselves then it might not be representative of the business which kind of defeats the whole point of domain driven design. The aim here is to end up with a system that represents the real-world domain that you’re trying to solve.

Working out all the domains is going to be an iterative process. You might find that one of the domains is huge and needs to be broken down further. Once you’ve worked out what all the domains are the next step is to work out the key parts of each of those domains.

| Bounded Context

If we take a closer look at the billing domain, we’ll probably find inside we’ll have things like subscribers, accounts, payment details, and subscription plans. What you’ll notice there’ll be parts that will be common across multiple subdomains. For example, the subscribers will likely come up across the whole system. The billing domain might call them subscribers or maybe even customers, whereas the streaming domain is more likely to call them viewers.

DDD copes with this by what is called a bounded context. Each subdomain will have its own bounded context, allowing the language to be used to be different for each of the subdomains. Bounded contexts are the Single Responsibility Principle applied to your domain model. Each part of your system has its intelligence, data, and vocabulary. Each part is independent of one another.

You don’t need to get the whole business to agree on what to call subscribers, you just need to agree on what to call them in each of the subdomains. If you’ve done a good job, you should find some clear separation between the different subdomains and the language used.

| Context map

Next we need to find out the relationships between them. We do this by creating what we call a context map, which outlines which domains communicate with each other, how they communicate and the direction that they’re communicating in. The interactions between the different subdomains will usually happen between the different entities.

The video streaming domain is going to need to know what quality of video to show to the viewers. This of course all depends on the subscription that they’re paying for. If they’re a basic user then they’ll get HD, if they’re a standard user they’ll get full HD and if they’re a premium user they’ll get ultra HD. The subscription plan however is outside the bounded context of the streaming domain. The streaming domain check with the billing domain to work out what quality video to show to the user. So we need to do a mapping between the viewer in the streaming domain and the subscriber in the billing domain.

Now to make sure that we don’t pollute either of the domains with information that doesn’t need to be there, we create what we call an anti-corruption layer, which does the translation between the domains for us. Once we have outlined all the domains and how they interact

Tactical Design

In this stage, we look at trying to refine our domain models a little bit further. To do this we look at each of our domains and try and work out what the objects are inside them. Now domain objects come in two forms.

| Entities

Each subdomain should have at least a few things that are unique to just that domain. For example, the billing domain will likely have payment details which you wouldn’t expect to see in any of the other domains. The aim here is to build up a model of all the different elements that make up each of the domains. The entities of a domain link to their real-world counterparts.

The entities of a domain link to their real-world counterparts. So in our example we might have subscribers as one of our entities. Each entity has an ID and it’s this ID that makes them unique. If all the other properties are exactly the same, if the IDs are different they are considered different entities. Entities are mutable, you can change their properties over time. For example a subscriber might change their email address but if the ID stays the same it is still considered the same subscriber.

| Value Objects

A value object, as the name suggests, generally corresponds to a value in your domain. Entities can have several different value objects in them. For example a subscriber is likely to have an email address as well as the date of birth. Value objects are not unique and two objects with the same value are considered to be equal. This ensures that when you compare them in your code the computer knows that they’re equal. Unlike entities, value objects are immutable. You can’t update them and if you need a different value then you just need to create a new one. We generally do this by only allowing values to be set in the constructor when the object is created, and then we don’t set up any setter methods to allow you to update them. The key thing to understand here is they are an object.

| IS this Value Object or Entity

When modelling your object, it can be difficult to decide whether something should be an entity or a value object. Generally, it depends on how important that object is in your domain model. For example, in many domains, an address is just information. It may be included in the billing details but it doesn’t actually have any importance in the system beyond that. Now imagine you’re creating a real estate application. Now the address isn’t just information it’s an important part of understanding the property. In this case the address would more likely be an entity than it would be a value object. Generally you want to have more value objects than entities in your domain. As value objects are small and immutable which makes them easier to work with.

| Aggregate.

An aggregate, as the name suggests, is a group of several entities and value objects. An example could be a customer’s order. It’s made up of the customer, the products they ordered, the order price, as well as information such as the shipping address. An aggregate also makes up a transactional boundary. So whenever changes are made to the aggregate, they should be either committed or rolled back to the database. This way we can ensure that the aggregate is always in a consistent state. Like entities, aggregates also have an ID which means they can be referenced from different parts of the application.

The aggregate is also responsible for maintaining business and variance. These are just business rules that always remain true no matter what you do to your system. For example, you might have a rule that says the order total needs to be a sum of all the products ordered. You might have another rule that stops people from ordering more than what is in stock. Obviously all of this comes at a cost. The more rules that you add into an aggregate, the longer it’s going to take to update it, which could affect user experience. So generally there is a bit of a trade-off between performance and consistency that you need to keep in mind. In some cases it might make more sense to set up what we call a corrective policy that runs on a regular basis to either flag or correct anything that might be wrong.

| Repositories and services

The repositories in this case, are the persistence layer for our aggregates so that they can be stored in the database. Then we have the services, which contain additional business logic, which either doesn’t fit in a single aggregate or spans multiple aggregates. Once you have everything mapped out, you’re ready to build your application.

Challenges

  1. Domain Complexity: Understanding and modeling complex business domains can be challenging. Domain experts may have differing interpretations of domain concepts, leading to inconsistencies in the domain model.
  2. Language Barrier: Bridging the gap between technical and domain-specific language can be difficult. Developers need to communicate effectively with domain experts to ensure the accuracy of the domain model.
  3. Bounded Contexts: Defining the boundaries of bounded contexts and maintaining their consistency can be challenging. Overlapping contexts or unclear boundaries can lead to integration issues and inconsistencies.
  4. Large-Scale Refactoring: Implementing DDD in existing systems often requires significant refactoring. This can be time-consuming and risky, especially in large codebases with complex dependencies.
  5. Technical Complexity: Applying DDD concepts such as aggregates, entities, and value objects effectively requires a deep understanding of both the domain and technical aspects of software development.
  6. Resistance to Change: Introducing DDD may face resistance from stakeholders who are accustomed to traditional development approaches. Convincing stakeholders of the benefits of DDD and overcoming organizational inertia can be challenging.
  7. Team Collaboration: DDD emphasizes collaboration between domain experts, developers, and other stakeholders. However, achieving effective collaboration across different roles and teams can be challenging, especially in large organizations with siloed departments.
  8. Tooling and Infrastructure: Supporting DDD may require specialized tools and infrastructure. Integrating these tools into existing development workflows and infrastructure can be challenging and may require additional investment.
  9. Learning Curve: DDD introduces new concepts and terminology, which may require developers to learn new skills. This learning curve can slow down initial development efforts and may require investment in training and education.
  10. Scope Management: Determining the appropriate scope for each bounded context and avoiding over-engineering or under-engineering can be challenging. Balancing the need for flexibility and scalability with the complexity of the system requires careful consideration.

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

All the credits goes to the Domain-Driven Design Reference: Definitions and Pattern Summaries by Eric Evan and
@alexhyettdev

--

--