Marc Denning

Book review: Domain-Driven Design

Domain Driven Design by Eric Evans seems to have a place as a classic in the software engineering and architecture space today. It is often referenced, and there are many derivative books and commentaries on implementing Evans' ideas and practices. After hearing about the book for many years, I decided to invest in it myself and give it a thorough read.

There are a few facets of the book I really appreciate:

In the remainder of this post, I will share some of my notes and ideas from the book that stood out to me.

Ubiquitous Language

Judging just by the amount of time that Evans spends talking about this, ubiquitous language is the most important topic in the book. The idea behind ubiquitous language is that it is a shared language between business stakeholders and developers to discuss the domain of a particular system. Evans comes back to this over and over during the book to emphasize and illustrate through various stories how being clear on and maintaining the ubiquitous language leads to more robust software designs and fewer mistakes over the lifetime of a project.

Domain Model

The domain model represents the core business domain concepts in the form of software design and code. It focuses on expressing the business rules rather than technical details. In my experience, it is difficult for a developer to separate business rules and concerns from technical ones, in particular when working with opinionated frameworks. I can recall numerous occasions when reviewing code that is new to me where I find myself confused in jumping between business concerns and technical ones like request handling or validation engines.

Combined with bounded contexts, the domain model forms the basis of where many conceptions of microservices seem to come from.

Layered Architecture

The concept of layers in software is probably not new to anyone who has been around programming very much. Evans describes a layered architecture as one that:

Similar to ubiquitous language and domain model, Evans emphasizes in his discussion of layered architecture the value of the business domain being encapsuated in its own layer.

Entities

In Domain-Driven Design, and entity is not synonymous with a database object. Instead, an entity is an object with a unique identity and state that can change over time and that represents a core domain concept. Often, and entity is persisted in a data store, but more important is the business context of the object.

Value Objects

Value objects are an idea I had a harder time getting my brain around. As I understand them, they are immutable objects that represent data attributes of an entity. However, they have no identity of their own and cannot be changed after creation. Given this definition, a value object may not be persisted in a data store directly, but is useful in representing an entity or domain concept.

One example might be: given a mapping application that stores Places in a database, a Place may have a Location that consists of a latitude and longitude. In code, the latitude and longitude may be represented by a Location object. While that data is stored in a database with other attributes of a Place, the Location itself is not persisted independently. It only helps describe the Place entity.

Aggregates

Given entities and value objects, aggregates combine related objects and treat them as a single unit for consistency purposes. For more complex domain models, it helps to have a single root entity and enforce invariants within the boundary.

If you're not familiar with the term invariant, I was not before reading the book, think of them as validation rules that can be enforced on an entity or aggregate that describe the business rules governing its attributes. For a given aggregate or entity, any invariants describing that object should never be violated.

Services

Remember, Evans is an advocate of the domain model being very robust and representative of how the real-world system works. With this backdrop, services represent actions or functionalities performed in the system that don't fit neatly into an entity. An important aspect of services as defined is that they are stateless and derived from the domain model.

Modules

The term module is used in many contexts and definitely has multiple meanings in the software development domain, something that Evans is quick to acknowledge. As he uses the term, he means: a logical grouping of code with high cohesion (related functionalities) and low coupling (dependencies). Importantly, modules should be named using terms from the ubiquitous language and still relate strongly to the domain module.

Factories

If you have worked with object-oriented programming for any length of time, you are likely familiar with factories. These are objects responsible for creating domain objects according to predefined rules. Some of the key reasons to develop and use a factory are to hide the complexity of object creation from client code and enforce the invariants of the entity or value object. I really like the metaphor that Evans uses to describe factories: a car does not assemble itself - a mechanic does that, but a mechanic doesn't necessarily operate the car either - that's left to the driver.

Supple Design

This concept is a bit more squishy and hard to identify, but describes the adaptability of a particular software system. I think of it as a goal for designing and building software. If a system has a supple design, then it emphasizes code maintainability and flexibility and allows for easier refactoring and iterative development. One could argue that a system design that follows the domain-driven design principles could more easily yield a supple design.

CQRS (Command Query Responsibility Segregation)

For me, this pattern experienced a surge in popularity a few years ago, and I was surprised to find that it is not new. Evans defined this as eparating read (queries) and write (commands) functionalities for better scalability and performance.

This is not a pattern I have personally run into very often in enterprise software, but the principles seem sound: separate the commands to allow the system to optimize each one. This may not fit very well with a hard-and-fast REST API, but I am not convinced REST is always appropriate anyway.

Anti-Corruption Layer

An anti-corruption layer is a layer in your system that translates between different domain models for integration purposes. The primary use case that Evans references for this pattern is when interfacing with external or legacy systems. I have seen this pattern deployed to help separate a greenfield system from a legacy one where the architects and system designers wanted to break free of some of the limitations of the legacy system. An anti-corruption layer or "ACL" helped them move more quickly in the desired direction for the greenfield application.

Bounded Contexts

As Evans defines them, bounded contexts build on the domain model and represent partitions of a large system with their own domain models and ubiquitous language. Almost inevitably in large systems, multiple domains start to be represented, even without architects and developers recognizing it. Naming bounded contexts can help provide clarity not only how to talk about the systems, but also how to organize the code and structures. When designing a microservices environment, software designers often define the bounded contexts and let independent services fall along those lines.

Evans again emphasizes communication and language when he notes the idea of a published language that can be used for communication between bounded contexts. To me, this feels parallel to technical specifications or API contracts but in terms of the business domain of the system - the ubiquitous language - as Evans refers to it.

Knowledge Level

Building on the concept of layers in software design, Evans describes a knowledge level as an abstraction layer that separates complex business logic from core domain entities. Remember that Evans is an advocate of a robust and well-defined business domain layer expressed as entities and value objects. To me, the knowledge level may serve as a bit of an abstraction or organizing method to the services that are developed to capture the behavior of entities. Evans cautions against designing this intentionally early in the development of a system, but rather to let it emerge later as the team's understanding of the domain model matures.

Pluggable Component Framework

One of the architectural patterns that Evans mentions late in the book is the pluggable component framework. This is an advanced architecture for complex systems with a well-defined core domain model. It allows for flexible implementations of the core functionalities. Similar to a knowledge level, Evans asserts that this type of pattern typically emerges late in the development of a project. I really appreciate that Evans is very pragmatic in his advice throughout the book. This is another instance where he cautions software designers against using abstractions too early or designing a system to be too complex for its immediate need. It is difficult to balance planning for the future in your design and code and focusing on the features and business needs right in front of you.

Conclusion

Domain Driven Design is an excellent book about software design. Evans provides a good deal of his own experience building systems to help other architects and developers make smarter choices for their projects. The book has a good blend of abstract concepts and practical examples to help drive ideas home. Evans takes a very practical approach to leveraging different patterns and practices, and I appreciate that he wrote the book to be both opinionated and accommodating of different environments and teams.

I will caution potential readers that it is a dense work - I had to read this in small sittings and take notes to really feel like I made the most of it. I understand now why there are derivative books, classes, and blog posts about how to apply these concepts - it takes practice to build a solid grasp of the tools Evans provides.

Note: this post was written with the assistance of Generative AI.