What you need to know - Tactical Design
In a previous post I introduced DDD's Strategic Design, how to handle the big picture. This post delves into the nuts and bolts of Domain Driven Design (DDD) - Tactical Design.
Vaugn Vernon eloquently compared Strategic Design to how a climber plans their route up a cliff and Tactical Design to the tools they use in his book, Implementing Domain Driven Design.
Tactical Design provides us with helpful pointers and constructs to create a rich, pure expression of business logic in Domain Models. It is most valuable when modelling a complex, or potentially complex domain.
Outside of DDD, entities are often modelled with numerous properties that can be changed by callers (clients) of the model code. Clients are left to enforce business rules (e.g. a price cannot be negative). The result: business logic is distributed across our application, often in Application/Service Layers or Controllers.
Tactical Design proposes that our Domain Model should be where we encapsulate all business logic relevant to the context/subdomain that the system serves. It lives in the ‘heart’ of our application, with no dependencies on outer architectural layers.
Behaviors of the system are exposed via public methods of the Domain Model that represent meaningful tasks a user wants to perform* (e.g. SubmitOrder, ReserveItem). Clients of the model can only call these methods of the model. The Domain Model is built using the Ubiquitous Language.
In your Tactical Design toolbox, there are constructs that make it simpler to model a domain effectively.
In Tactical Design, an Entity is something with an identity. Its properties may change over time, but the Entity remains the same Entity. It is defined by its identity, not the values of its properties.
Consider you, a human (most likely). If you change your name, you’re still the same human. If you have an identical twin with identical properties, the two of you are not the same human. Entities’ intrinsic identities are independent of their properties.
A Value Object, unlike an Entity, is defined entirely by it’s values and has no identity. Consider the color brown; it can’t change, otherwise it would be a different color. It is defined by its values and is immutable.
One particularly helpful type of Value Object is a Command, which expresses a call for action that the system must respond to.
Favor Value Objects over primitive types as properties of Entities to encapsulate validation or business logic associated with Value Objects, for example Mass can’t be negative (in most domains!).
Aggregate / Aggregate Root
Often, we require validation across multiple entities to enforce business rules.
“A new user’s email address must be unique”
To enforce this rule, we must compare the address provided against the addresses of existing users. We build Aggregates, collections of Entities and Value Objects in a single object, to enable this sort of validation in our models.
The top-level object within an Aggregate is called the Aggregate Root. Mutation of objects within the Aggregate must occur via the Root. Aggregates may reference other Aggregates, but only via Aggregate Root Ids, to preserve encapsulation.
When we talk to domain experts, they often refer to things that have happened and responses to these events. We use Domain Events and corresponding handlers to model this.
Sometimes Integration Events are also included, to distinguish between events that should be handled before or after a successful database transaction, though this can also be handled in other ways.
The above four constructs are the core building blocks of our domain models, with just these we can create clean models of complex domains.
Consider an Order Aggregate Root with the method: Confirm(). Within this method, we may validate that the Order contains one or more OrderItems and the Order’s OrderStatus is ‘Pending’, before updating OrderStatus to ‘Confirmed’ and raising an OrderConfirmed Domain Event. We can handle this event to implement side effects across other Aggregate Roots, such as the Delivery Bounded Context’s Shipments Aggregate Root.
A Repository in DDD exclusively operates on Aggregate Roots. It is responsible for obtaining an Aggregate Root, on which we may call a public method that mutates the internal state before persisting the updated Aggregate Root. Popular ORMs have excellent built-in support for handling this scenario. Repository implementations do not exist inside the Domain Model, although their interfaces/contracts may do so.
Application Services provide routing of Commands/Requests from the outside world into our model. They form a thin orchestration layer where infrastructural concerns are injected (like repository implementations) and the relevant public methods of Aggregate Roots are called.
Care should be taken to keep Application Services ‘dumb’, we want ALL the business logic in the Core of our application.
What happens when we must orchestrate functionality across numerous Aggregates? Couldn’t that constitute ‘business logic’?
It could! One solution is a Domain Service. It is similar to an Application Service, but lives inside the Core Domain. It contains orchestration logic that constitutes business logic. Often, this chain of events is better expressed through Domain Events and corresponding handlers.
Math is useful if all you can do is basic arithmetic. It’s also useful if you can do tensor calculus, but you don’t start out with this.
In a similar way, DDD is a deep discipline. There are numerous books that range from highly academic to abundantly practical. The famous Blue Book is the original reference on the topic; it provides a detailed definition of DDD from a fairly academic viewpoint. Conversely, the Red Book tells the stories of three development teams in their adoption of DDD, which makes for a natural, practical expose of DDD’s concepts. For auditory learners there are numerous talks that are enlightening, such as those by fellow thoughtworker Andrew Harmel-Law.
I hope these two short blogs have introduced you to the key concepts on a medium-high level, so you can confidently continue on your path to DDD-mastery however suits you best!
a.k.a. Task Based Interface
Disclaimer: The statements and opinions expressed in this article are those of the author(s) and do not necessarily reflect the positions of Thoughtworks.