Domain-Driven Design: Effective Domain Modeling and its Perks

Mahad Khan
SSENSE-TECH
Published in
11 min readJul 30, 2021

According to Eric Evans, Domain-Driven Design (DDD) is a way of thinking that revolves around mapping business domain concepts into software artifacts. It emphasizes modeling the domain in such a way that it can bring benefits not only from a technical standpoint but also from a business standpoint.

In our last SSENSE-TECH article about DDD, we discussed the strategic and tactical design as well as the collaborative modeling DDD introduces. In this article, we will attempt to compare DDD with the traditional and commonly used layered architecture that is prone to anemic domain models. We will also list some key characteristics that the domain model should have — should you choose to employ DDD — as well as discuss the business value that DDD can bring to your organization.

Traditional Layered Architecture vs DDD

Implementing a rich domain model is an integral part of DDD. It involves building blocks like Entities, Value Objects, and Aggregates. The opposite of rich domain models are the anemic domain models. In anemic domain models, entities are represented by classes that include only data and connections to other entities. Business logic is absent in these classes and is usually placed in managers, services, utilities, helpers, etc.

Anemic Domain Models

For large and complex applications, the lack of investment in proper domain modeling and development efforts can lead to an architecture with a fat Service Layer and an anemic domain model where facade classes accumulate more business logic with time and domain objects become mere data carriers with getters and setters. Since proper responsibility boundaries are not defined, domain-specific business logic and responsibilities may leak into other components.

The diagram below illustrates a 3-layer application with an anemic domain model.

Anemic Domain Model

A Data Access Layer (DAL) consists of classes responsible for reading and writing the entities. In most cases, since each entity represents a separate business case, the number of Data Access Objects (DAO) is equivalent to that of entities. The DAOs do not include any business logic and are just tools for retrieving and persisting entities.

The Service Layer includes modules and classes that utilize an appropriate DAO to implement the business logic for a particular functionality. A typical service will always perform operations like loading an entity leveraging a DAO, modifying the entity’s state and persisting it. Martin Fowler describes this architecture as a series of Transaction Scripts. As functionalities become more complex, the number of operations between loading and persisting entities increase. Since the entities and DAOs are not modified unless new fields are required to be persisted, this leads to services making use of other services, eventually leading to, what’s called, fat Service Layer.

Domain-Driven Design

DDD on the other hand, consists of an architecture with a rich model and thin services. This is represented in the diagram below.

Domain-Driven Design

As can be noted, the Service Layer in an application that employs DDD is much thinner than its counterpart in the anemic model. Each service only includes operations that work on different areas of the domain. This is possible because now most of the business logic is included in the Domain Layer and not in the services.

If we compare the two diagrams above, we can see that the Domain Layer in DDD is thicker and contains more objects. It now includes value objects, entities, and aggregates. You can learn more about these and many other important components of DDD in the article ‘Domain-Driven Design: Everything You Always Wanted to Know About it, But Were Afraid to Ask’.

Finally, we can also note that the last layer in this model is thinner than that in the previously depicted architecture. In DDD, each aggregate requires one repository. This means we have fewer repositories in DDD than the number of DAOs required to load and persist entities in an application with an anemic model.

The Refactoring Journey

Next, we will take you through the refactoring of a simple order management application with the typical layered structure and anemic domain models to one modeled according to the DDD principles. Our order management application, through REST API, can perform the following business operations:

  • Create a new order
  • Change order status
  • Add an item to an existing order

Some validation rules apply to the operations:

  • Allowed status transitions : NEW -> PACKED -> SHIPPED.
  • An item can not be added to an order whose current status is SHIPPED.

Application with Anemic Domain Models

Our application with anemic domain models has been segregated into four packages, each responsible for a layer of the application as can be seen in the image below:

To complete an operation, the controller handles HTTP requests, extracts the relevant data and passes them to the service. The service contains all the business logic. It loads entities from the repository, performs some business operations and if necessary, persists and returns the modified objects back to the controller. The controller finally outputs some data in the relevant format.

The following is an example of service operations to change an order status and to add an item to an order:

Refactoring to DDD

Packages:

To start off, we will first repackage our application. In our application following DDD principles, we have the following package structure:

The Domain Layer will contain the entities and value objects along with the repository interface. All the business logic will live in this layer.

The UI Layer will contain the controller. The Application Layer will contain the services. There is now a separate service for each operation that is performed on a specific area of the domain.

The Infrastructure Layer will contain the implementation of the interfaces defined in the domain, as well as classes that provide this implementation. Any operations that require our application to communicate with the outside world should be implemented in this layer too.

It may be worthwhile to note that if we want to add a new module, for example ‘Customer’, according to DDD concepts we must realize that it is a different bounded context. We should then create two separate root packages: ‘Order’ and ‘Customer’.

Domain:

In order to refactor the domain according to the DDD concepts, we must identify the aggregates, entities, and value objects. In our example, Order and Item are separate entities but it’s useful to treat the Order together with its Items as a single aggregate. Properties of the Order and Items such as OrderStatus, Price, Quantity, and SKU can be treated as value objects. This will ensure that these fields are always valid.

In DDD, since the Order aggregate must contain all the business operations that can be performed on it, we need to move the logic to change the order status and to add an item, from the Service Layer to the Domain Layer, therefore this logic should reside in the Order Aggregate.

One benefit of moving the business operations to the Domain Layer would be that now all the validation logic can be placed inside the object which is being modified. It is also easier to understand exactly what can be done to an object from the business perspective. Furthermore, since no public modifiers are available any more, an inconsistent state of an object can no longer be introduced.

Service:

Since the business logic has now been moved from the service to the domain aggregate, we can refactor our service such that it only loads the aggregate from the repository, modifies it by calling a function defined in the domain and then persists this modified object.

Below is an example of the service responsible for changing the order status:

Repository:

In our application with anemic domain models, we had two repositories; one for Order and another for OrderItem. The repository interface was directly coupled to its specific implementation.

In DDD, we define a single repository interface with all the required functions in the Domain Layer.

This interface is implemented in the Infrastructure Layer. This makes it easier to replace our repository provider with another one depending on our usage since our domain is no longer coupled with it.

We could go further and find an order using an Id value object instead of a string. This way, we ensure that we do not search the repository by providing a different string from another entity.

With the example above, we can note that the employment of DDD revolves around designing a rich domain model. Furthermore, it requires some structural changes to an application following the traditional layered architectures. Although these improvements can be time and resource-intensive at first, eventually they will help our application be easier to maintain, well organized and stable.

The Characteristics of a Rich Domain Model

Before diving into the business value that DDD can bring to an organization, I believe it is important to mention a few characteristics of a rich domain model. The domain model should:

  • Focus on a specific business operational domain and align with the business’ model, strategies, and processes.
  • Be reusable, as to help avoid duplication and implementations of the same core business domain models.
  • Be isolated from other business domains as well as other layers in the application’s architecture.
  • Be loosely coupled with other layers such that there is no dependency on the layers on either side of the Domain Layer.
  • Be abstract with a cleanly separated Domain Layer which allows for easier maintenance, testing, and versioning.
  • Be designed using a programming model that defines an object whose fields can only be accessed via the constructor or getter/setter methods, and does not have any technology or framework dependencies.
  • Not include any persistence implementation details. The Domain Layer, however, defines the interfaces that the repositories in the Infrastructure Layer implement. The reason behind this is explained by the next point.
  • Have minimum dependencies on infrastructure frameworks. The domain model can outlive these, thus we want to avoid tight coupling with external frameworks.

The Business Value that DDD Brings

Before proposing a technology or technique to our management, domain experts, and technical team, we must make sure that it brings tangible business value. If we can demonstrate that the suggested approach brings greater value to the business than other options, our business case is strengthened. The employment of DDD has its own set of values and benefits that it can provide to the business.

DDD emphasizes its focus on the core domain model where all the business logic lives. The other models that support the core domain may not be given the same level of attention the core domain receives. This allows the organization to gain a useful model of its domain and achieve a competitive advantage. With time, as this model is refined, the organization develops a deeper understanding of the core business. As we are challenged by one another, the domain experts may think of greater details which the technical team members implement. These details can be used as a tool by the business to analyze the strategic and tactical value of its direction.

Furthermore, domain experts may not always agree on concepts and terminology. However, as they are brought together to contribute to the software design, they gain consensus among themselves which is beneficial for the organization. Since DDD efforts require domain experts and developers to work closely together, they now share a common language, and there is a greater transfer of knowledge between them, which allows common knowledge to be agreed on by all the members in the unified team. This makes training and handoffs easier.

Often software that is not domain-driven leads to a compromised user experience. If users do not fully understand what data they need to feed into the system, they may have to spend time learning how the software works, which in turn can lead to lowered productivity. Software that is designed with the DDD approach can train the user himself, as the user experience is designed with the underlying expert model in mind. The resulting improved user experience allows its users to be more productive, faster, and with less training, which means more business value and better ROI.

DDD not only adds collaborative but also technical value to the business. For example, when the technical team aligns their development expectations with business advantage, their solutions are directed to what matters most to the business. When bounded contexts are defined with explicit boundaries and the relationships between them are well understood, the enterprise architecture is better organized. A thorough understanding of the enterprise architecture is developed when context maps are employed to establish formal relationships and ways to integrate models that intersect by usage dependency.

When designing the domain model, it is recommended that the team should leverage an agile approach. This iterative and incremental approach helps to continuously refine the mental model of the domain experts into a useful model, in other words, the working software for the business. DDD also employs new tools, both strategic and tactical. Each bounded context provides the team with a modeling boundary in which they should create solutions to specific business problems. The team formulates a ubiquitous language respective to the bounded context which is used among the team and in the software model. Within each of these modeling boundaries, the team can use various beneficial tactical modeling tools such as aggregates, entities, value objects, services, and domain events.

Conclusion

Now, given the above information, is employing DDD really worth it? Well, there is no solid answer for this question as it really depends on what kind of application you’re building. For simple CRUD applications — which do not involve a lot of business logic — you may want to stick to the anemic model. However, if your application is large and complex, DDD may definitely be worth considering. It can allow you to simplify business logic implementation, provide a better separation of concerns leading to a much cleaner design of each layer, along with the many other benefits presented in this article.

At SSENSE, we have been putting DDD in practice as it allows us to solve complex business problems and implement functionalities, keeping a long term vision of our software products. Additionally, it allows for an iterative development cycle in close coordination with domain experts to incorporate further business logic, while keeping our systems well organized and stable.

To further seek and avail the benefits of DDD, I encourage you to take a look at ‘Domain-Driven Design: Tackling Complexity in the Heart of Software’, authored by Eric Evans as well as ‘Implementing Domain-Driven Design’ authored by Vaughn Vernon.

Editorial reviews by Liela Touré & Pablo Martinez

Want to work with us? Click here to see all open positions at SSENSE!

--

--