Domain-Driven Design: Everything You Always Wanted to Know About it, But Were Afraid to Ask
By: Pablo Martinez
Tags: Software Engineering, Domain Driven Design, Software, Design Patterns, Software Architecture

Tactical Design: The Nuts and Bolts of DDD

Implementing associations between domain objects and describing their functionality feels easy at a glance, but proper distinction of their meaning and reason of existence should be done in a clear and intuitive way. DDD proposes a set of constructs and patterns to achieve it.

Entities

Objects that have a unique identity and possess a thread of continuity are called Entities, they are not defined solely by their attributes, but more by who they are. Their attributes might mutate and their life cycles can drastically change, but their identity persists. Identity is maintained via a unique key or a combination of attributes guaranteed to be unique.

In the e-commerce domain for example, an order has a unique identifier and it goes through several different stages: open, confirmed, shipped, and others, therefore it’s considered a Domain Entity.

Value Objects

Objects that describe characteristics, and that do not possess any unique identity are called Value Objects, they care only about what they are, not who they are.

Value Objects are attributes of, and can be shared by multiple entities, for example: two customers can have the same shipping address. Although there’s a risk — if one of their attributes needs to change, all the entities that share them would get impacted. To prevent this, Value Objects must be immutable, forcing the system to replace them by fresh new instances, when an update is required.

Also, Value Object creation should always depend on the validity of the data used to create them and how it respects business invariants. Therefore, if data is invalid, the object instance will not be created. For example, in North America, a postal code with non-alphanumeric characters would violate a business invariant and would trigger an exception on the Address creation.

Services

In many cases the Domain Model requires certain actions or operations that are not directly related to an Entity or a Value Object, forcing these into their implementations causes a distortion of their definition. Services are classes that offer stateless operations. They are commonly named as verbs, as opposed to nouns for Entities and Value Objects, and are named based on the Ubiquitous Language.

Services should be carefully crafted, always ensuring that they do not strip Entities and Value objects of their direct responsibilities and behavior. They should also be stateless, such that clients can use any given instance of a Service disregarding that instance’s history during the application’s lifetime. Having Entities and Value Objects with no domain logic is considered an anti-pattern called the Anemic Domain Model.

Domain Objects and their Life Cycle

Domain objects commonly have complex life cycles, they are instantiated, go through several changes, interact with other objects, execute operations, get persisted, get reconstituted, get deleted, and so on. Maintaining their integrity while ensuring that the system doesn’t mismanage their complex lifecycle is one of the main challenges that implementing a proper Domain Model represents.

Minimizing the relationship and interaction between domain objects to maintain manageable levels of complexity within the Domain Model is difficult, especially in complex business domains, or as Eric Evans describes it:

“It is difficult to guarantee the consistency of changes to objects in a model with complex associations. Invariants need to be maintained that apply to closely related groups of objects, not just discrete objects. Yet cautious locking schemes cause multiple users to interfere pointlessly with each other and make a system unusable”

Aggregates

To mitigate the aforementioned challenge, an aggregation of Entities and Value Objects is needed, restricting the violation of business invariants.

Aggregates are collections of related Entities and Value Objects, clustered together representing a transactional boundary. Each Aggregate has an Entity that faces outwards and controls all access to the objects inside the boundary, this entity is called the Aggregate Root, and it’s the only object that other objects can interact with. No objects within the Aggregate can be called directly from the outside world, thus maintaining consistency within.

Business Invariants are business rules that guarantee the integrity of an Aggregate and its contents, in other words, it is a mechanism that ensures that their state is always consistent with the business rules. For example, an order can never be placed when the stock quantity of a certain product is zero.

Factories

Creating complex object and aggregate instances can be a daunting task, and can also disclose too much of the object’s internal details. Using Factories, we can solve this problem and provide the necessary encapsulation.

A Factory should be able to construct domain objects or aggregates in one atomic operation, requiring all data needed to be provided by the client when called, and enforcing all invariants on the created object. This activity is not part of the Domain Model, but still belongs in the Domain Layer as it is part of the business rules that apply to the system.

Repositories

To be able to retrieve objects from our persistence, be it in-memory, filesystem, or database, we need to provide an interface that hides the implementation details from the client, so that it does not depend on the infrastructure specifics, but merely on an abstraction.

Repositories provide an interface that the Domain Layer can use to retrieve stored objects, avoiding tight-coupling with the storage logic and giving the client an illusion that the objects are being retrieved directly from memory.

It’s important to mention that all repository interface definitions should reside in the Domain Layer, but their concrete implementations belong in the Infrastructure Layer.

Isolating the Domain from Other Concerns

The part of the code that is written to specifically solve domain problems is a fraction of the whole codebase. If this portion is intertwined with code that solves other concerns, it will be difficult to understand and improve. Clearly separating domain logic from all other functionality will reduce the leakage and will avoid confusion in a large and complex system.

DDD proposes a Layered Architecture as a way to separate concerns and avoid responsibility confusion by splitting the codebase in 4 main layers: User Interface, Application, Domain, and Infrastructure.

The main rule here is that components in each layer should depend only on components in the same layer or any layer beneath it. Upper layers can use components of lower ones just by calling their public interfaces and lower layers can only communicate upwards by Inversion of Control (IoC).

User Interface Layer: is responsible for displaying data and capturing user’s commands.

is responsible for displaying data and capturing user’s commands. Application Layer: serves as an orchestrator of domain work, it does not know domain rules, but organizes and delegates domain objects to do their job. It is also the only layer accessible to other bounded contexts.

serves as an orchestrator of domain work, it does not know domain rules, but organizes and delegates domain objects to do their job. It is also the only layer accessible to other bounded contexts. Domain Layer: holds the business logic and rules, as well as the business state. It is where the Domain Model lives.

holds the business logic and rules, as well as the business state. It is where the Domain Model lives. Infrastructure Layer: implements all the technical functionalities the application needs to support the higher layers, persistence, messaging, communication between layers, etc…

Even though not all layers are required in every system, the existence of the Domain Layer is a prerequisite in DDD.