Domain Driven Design: Tackling Complexity in the Heart of Software

I am writing this book summary to help me COMMIT; all key takeaways from this book in my memory, and also for my future reference (learning purpose). I also add some comments that I personally find relevant.

The “blue book”, by Eric Evans

The “blue book”, by Eric Evans

To create software that is valuably involved in users’ activities, a development team must bring to bear a body of knowledge related to those activities. The breadth of knowledge required can be daunting. The volume and complexity of information can be overwhelming. Models are tools for grappling with this overload. A model is a selectively simplified and consciously structured form of knowledge. An appropriate model makes sense of information and focuses it on a problem.

The Heart of Software

The heart of software is its ability to solve domain-related problems for its user. All other features, vital though they may be, support this basic purpose. When the domain is complex, this is a difficult task, calling for the concentrated effort of talented and skilled people. Developers have to steep themselves in the domain to build up knowledge of the business. They must hone their modeling skills and master domain design.

Ingredients of Effective Modeling

  1. Binding the model and the implementation
  2. Cultivating a language based on the model
  3. Developing a knowledge-rich model. The objects had behavior and enforced rules. The model wasn’t just a data schema; it was integral to solving a complex problem. It captured knowledge of various kinds.
  4. Distilling the model. Important concepts were added to the model as it became more complete, but equally important, concepts were dropped when they didn’t prove useful or central.
  5. Brainstorming and experimenting. The language, combined with sketches and a brainstorming attitude, turned the discussions into laboratories of the model, in which hundreds of experimental variations could be exercised, tried, and judged.

Knowledge Crunching

Knowledge crunching IS NOT a solitary activity. A team of developers and domain experts collaborate, typically led by developers. Together they draw in information and crunch it into a useful form. The raw material comes from:

It comes in the form of documents written for the project or used in the business, and lots and lots of talk. Early versions or prototypes feed experience back into the team and change interpretations.

Bad – In the old waterfall method, the business experts talk to the analysts, and analysts digest and abstract and pass the result along to the programmers, who code the software. This approach fails because it completely lacks feedback. The analysts have full responsibility for creating the model, based only on input from the business experts. They have no opportunity to learn from the programmers or gain experience with early versions of software. Knowledge trickles in one direction, but does not accumulate.

Bad – Other approach is iterative process. Developers get the experts to describe a desired feature and then they go build it. Good programmers will naturally start to abstract and develop a model that can do more work. But when this happens only in a technical setting, without collaboration with domain experts, the concepts are naive. That shallowness of knowledge produces software that does a basic job but lacks a deep connection to the domain expert’s way of thinking.

Good – The interaction between team members changes as all members crunch the model together. The constant refinement of the domain model forces the developers to learn the important principles of the business they are assisting, rather than to produce functions mechanically. Because analysts and programmers are feeding into it, it is cleanly organized and abstracted, so it can provide leverage for the implementation. Because the domain experts are feeding into it, the model reflects deep knowledge of the business. The abstractions are true business principles.

By the way, knowledge crunching is an exploration, and you can’t know where you will end up.

Continuous Learning

When we set out to write software, we never know enough. Knowledge on the project is fragmented, scattered among many people and documents, and it’s mixed with other information so that we don’t even know which bits of knowledge we really need. Domains that seem less technically daunting can be deceiving: we don’t realize how much we don’t know. This ignorance leads us to make false assumptions.

Highly productive teams grow their knowledge consciously, practicing continuous learning. For developers, this means improving technical knowledge, along with general domain-modeling skills.

Knowledge-rich Design - Extracting Hidden Concept

// Allow 10% overbooking.
// Initial version

public int makeBooking(Cargo cargo, Voyage voyage) {
   double maxBooking = voyage.capacity() * 1.1;
   if ((voyage.bookedCargoSize() + cargo.size()) > maxBooking)
       return –1;
   int confirmation = orderConfirmationSequence.next();
   voyage.addCargo(cargo, confirmation);
   return confirmation;
}
// Better version

public int makeBooking(Cargo cargo, Voyage voyage) {
   if (!overbookingPolicy.isAllowed(cargo, voyage)) return –1;
   int confirmation = orderConfirmationSequence.next();
   voyage.addCargo(cargo, confirmation);
   return confirmation;
}

// Overbooking Policy class
public boolean isAllowed(Cargo cargo, Voyage voyage) {
   return (cargo.size() + voyage.bookedCargoSize()) <=
         (voyage.capacity() * 1.1);
}

It will be clear to all that overbooking is a distinct policy, and the implementation of that rule is explicit and separate.

Ubiquitous Language

For first you write a sentence,
And then you chop it small;
Then mix the bits, and sort them out
Just as they chance to fall
The order of the phrases makes
No difference at all.

Lewis Carroll, “Poeta Fit, Non Nascitur”

Domain experts have limited understanding of the technical jargon of software development, but they use the jargon of their field. Developers, on the other hand, may understand and discuss the system in descriptive, functional terms, devoid of the meaning carried by the experts’ language.

Across this linguistic divide, the domain experts vaguely describe what they want. Developers, struggling to understand a domain new to them, vaguely understand. A few members of the team manage to become bilingual, but they become bottlenecks of information flow, and their translations are inexact.

On a project without a common language, developers have to translate for domain experts. Domain experts translate between developers and still other domain experts. Developers even translate for each other.

The indirectness of communication conceals the formation of schisms—different team members use terms differently but don’t realize it. This leads to unreliable software that doesn’t fit together.

A project faces serious problems when its language is fractured. Domain experts use their jargon while technical team members have their own language tuned for discussing the domain in terms of design.

The terminology of day-to-day discussions is disconnected from the terminology embedded in the code (ultimately the most important product of a software project). And even the same person uses different language in speech and in writing, so that the most incisive expressions of the domain is never captured in the code or even in writing.

Ubiquitous Language

The vocabulary of that Ubiquitous Language includes the names of classes and prominent operations. You need to use the model as the backbone of a language. Commit the team to exercising that language relentlessly in all communication within the team and in the code. Use the same language in diagrams, writing, and especially speech.

If sophisticated domain experts don’t understand the model, there is something wrong with the model.

Layered Architecture

Layered Architecture

Partitioning into Layers

Partitioning into Layers

Building Blocks of Model-driven Design

building-blocks-model-driven-development

We can express model with Entities (also known as Reference Objects), Value Objects and Services.

And we can manage the life cycle of the domain objects using Aggregates, Factories, and Repositories

Entities vs. Value Objects

The attributes that make up a Value Object should form a conceptual whole. For example, street, city, and postal code shouldn’t be separate attributes of a Person object. They are part of a single, whole address, which makes a simpler Person, and a more coherent Value Object.

entities-value-object

Example 1 – In software for a mail-order company, an address is needed to confirm the credit card, and to address the parcel. But if a roommate also orders from the same company, it is not important to realize they are in the same location. Address is a Value Object.

Example 2 – In software for the postal service, intended to organize delivery routes, the country could be formed into a hierarchy of regions, cities, postal zones, and blocks, terminating in individual addresses. These address objects would derive their zip code from their parent in the hierarchy, and if the postal service decided to reassign postal zones, all the addresses within would go along for the ride. Here, Address is an Entity.

Example 3 – In software for an electric utility company, an address corresponds to a destination for the company’s lines and service. If roommates each called to order electrical service, the company would need to realize it. Address is an Entity. Alternatively, the model could associate utility service with a “dwelling,” an Entity with an attribute of address. Then Address would be a Value Object.

Services

When a significant process or transformation in the domain is not a natural responsibility of an Entity or Value Object, add an operation to the model as a standalone interface declared as a Service. Define the interface in terms of the language of the model and make sure the operation name is part of the Ubiquitous Language. Make the Service stateless.

Most Services discussed in the literature are purely technical and belong in the infrastructure layer. Domain and application Services collaborate with these infrastructure Services. For example, a bank might have an application that sends an e-mail to a customer when an account balance falls below a specific threshold. The interface that encapsulates the e-mail system, and perhaps alternate means of notification, is a Service in the infrastructure layer.

Application Services vs. Domain Services

If the banking application can convert and export our transactions into a spreadsheet file for us to analyze, that export is an application service. There is no meaning of “file formats” in the domain of banking, and there are no business rules involved.

On the other hand, a feature that can transfer funds from one account to another is a domain service because it embeds significant business rules (crediting and debiting the appropriate accounts, for example) and because a “funds transfer” is a meaningful banking term.

Aggregates

An Aggregate is a cluster of associated objects that we treat as a unit for the purpose of data changes. Each aggregate has a root and a boundary.

Entities other than the root have local identity, but that identity needs to be distinguishable only within the Aggregate, because no outside object can ever see it out of the context of the root Entity.

Aggregates

Cluster the Entities and Value Objects into Aggregates and define boundaries around each. Choose one Entity to be the root of each Aggregate, and control all access to the objects inside the boundary through the root. Allow external objects to hold references to the root only.

Factories

When creation of an object, or an entire Aggregate, becomes complicated or reveals too much of the internal structure, Factories provide encapsulation.

Factories

Creation of an object can be a major operation in itself, but complex assembly operations do not fit the responsibility of the created objects. Combining such responsibilities can produce ungainly designs that are hard to understand.

The factory pattern written by “Gang of Four” will help here.

Repositories

A Repository represents all objects of a certain type as a conceptual set (usually emulated). It acts like a collection, except with more elaborate querying capability. Objects of the appropriate type are added and removed, and the machinery behind the repository inserts them or deletes them from the database (Note: the implementation of Repository isn’t always using ORM).

repositories

A Repository doing a search for a client

Provide Repositories only for Aggregate Roots that actually need direct access. Keep the client focused on the model, delegating all object storage and access to the Repositories.

repositories

A flexible, declarative specification of search criteria in a sophisticated Repository

Repositories + Factories Combined

A Factory handles the beginning of an object’s life; a Repository helps manage the middle and the end. Here’s how those two combined.

Example 1:

img

A Repository uses a Factory to reconstitute a preexisting object

Example 2:

img

A client uses a Repository to store a new object

Strategic Design of DDD

As systems grow too complex to know completely at the level of individual objects, we need techniques for manipulating and comprehending large models. To help accomplish these goals, there are 3 basic principles that we need to keep in mind: context, distillation, and large-scale structure.

Large-scale structure brings consistency to disparate parts to help those parts mesh. Structure and distillation make the complex relationships between parts comprehensible while keeping the big picture in view. Bounded Contexts allow work to proceed in different parts without corrupting the model or unintentionally fragmenting it. Adding these concepts to the team’s Ubiquitous Language helps developers work out their own solutions.

Bounded Context

Multiple models coexist on big projects, and this works fine in many cases. Different models apply in different contexts. For example, you may have to integrate your new software with an external system, which your team has no control. A situation like this is probably clear to everyone as a distinct context, but other situations can be more vague and confusing.

bounded-context

Example of Bounded Context – Two Bounded Contexts formed to allow efficient routing algorithms to be applied

Context Map

An individual Bounded Context still does not provide a global view. The context of other models may still be vague and in flux.

img

Context Map

The map does not have to be documented in any particular form. Diagrams like the above is helpful in visualizing and communicating the map. Others may prefer a more textual description or a different graphical representation. In some situations, discussion among teammates may be sufficient. The level of detail can vary according to need. Whatever form the map takes, it must be shared and understood by everyone on the project.

Anticorruption Layer

New systems almost always have to be integrated with legacy or other systems, which have their own models. Translation layers can be simple, even elegant, when bridging well-designed Bounded Contexts with cooperative teams. But when the other side of the boundary starts to leak through, the translation layer may take on a more defensive tone.

An Anticorruption Layer is meant to link two Bounded Contexts, but prevent integration chaos between those two. Typically, we are thinking of a system created by someone else; we have incomplete understanding of the system and little control over it.

Create an isolating layer to provide clients with functionality in terms of their own domain model. The layer talks to the other system through its existing interface, requiring little or no modification to the other system. Internally, the layer translates in both directions as necessary between the two models.

One way of organizing the design of the Anticorruption Layer is as a combination of Facades, Adapters (both from here), and translators, along with the communication and transport mechanisms usually needed to talk between systems.

acl

The structure of an Anticorruption Layer

Distillation

Distillation is the process of separating the components of a mixture to extract the essence in a form that makes it more valuable and useful. A model is a distillation of knowledge.

Distillation is the process of identifying the domain, decoupling it, isolating it, clarifying it and making it explicit, allowing us to focus on what is more relevant and having it maintainable.

The artifact of distillation can be living documents that explain the domains (hence, distill).

Large-scale Structure

Software designs tend to be very abstract and hard to grasp. Developers and users alike need tangible ways to understand the system and share a view of the system as a whole.

In a large system without any overarching principle that allows elements to be interpreted in terms of their role in patterns that span the whole design, developers cannot see the forest for the trees.

A “large-scale structure” is a language that lets you discuss and understand the system in broad strokes. A set of high-level concepts or rules, or both, establishes a pattern of design for an entire system.

We need to establish Responsibility Layer to establish a large-scale structure language. When you gain a deep understanding of a domain, broad patterns start to become visible.

responsibility-layer

Responsibility Layer

Bringing the Strategy Together

The three basic principles of strategic design (context, distillation, and large-scale structure) are not substitutes for each other; they are complementary and interact in many ways. For example, a large-scale structure can exist within one Bounded Context, or it can cut across many of them and organize the Context Map.

img

A structure that allows some components to span layers


img

Combining Large-scale Structure and Distillation. Modules of the Core Domain (in bold) and Generic Subdomains are clarified by the layers.