This page looks best with JavaScript enabled

Architecture - DDD Tactics

 ·   ·  ☕ 4 min read

A new Domain.

Introduction

We’ll tackle the Tactical side of DDD here, and leave the Strategic ideas for another post.

When talking about layers within the context of DDD, we can separate 4 of them and understand them based on concepts from EBI:

  • User Interface: More or less equivalent to Boundaries in EBI, only this time the Domain is further down.
  • Application: Partly in charge of the role of the Interactor, specifically related to use case orchestration.
  • Domain: In line with the Entities from EBI
  • Infrastructure: Simply in charge of persistence, messaging, and such.

Entities / Value Objects

Concrete representations of very basic Domain concepts.
The difference relies on mutability and identity:

An Employee might change their role within the company.
This doesn’t mean we are talking about a different employee, so it’s reasonable for them to have some sort of ID to tell one apart from another, no matter which of their attributes change.
This is an Entity.

A phone number on the other hand does not change.
Or rather, if it does, we are talking about a completely different phone number.
It wouldn’t really make sense for a phone number to have an ID: The object is identifiable by its very nature.
This is a Value Object.

TL;DR: Entities have IDs, Value Objects (VO) don’t.

As you might imagine, not all cases are so clear-cut as the example above.
The context and Domain will dictate which to use in a given situation.

It’s also important to consider that, as usual, neither should be anemic.
No matter which one you use, they should encapsulate as much logic as reasonably possible (usually all logic regarding their individual behavior).

Aggregates

Conceptual elements made up of multiple Entities and/or VO, which only have meaning or make sense together.

The concrete representation of this element is the Aggregate Root. This serves as a gateway to the rest of the elements enclosed within the Aggregate (Entities and VO).
The Aggregate Root should be the only way of accessing those elements, especially when modifying their state.

One could easily imagine such a structure getting out of hand.
Prevent this from happening by:

  • Keep them as small as possible.
  • Allow for easy promotion from Entity to Aggregate Root, in case one of them grows significantly.
  • Aggregates should relate to one another by ID or directly through Services or Events to maintain scalability.

All logic pertaining multiple Aggregates should be delegated to a Domain Service.

Services

Stateless objects that perform Domain-specific operations that escape the boundary of the Aggregate.
Based on their scope, there are two kinds:

  • Domain services: Execute logic that does not fit nicely within an Entity or Value Object. Orchestrates several Entities interacting with the relevant Aggregate Roots.
  • Application services: Orchestrates a Use Case, using Repositories, Domain Services, Entities, Value Objects or any other Domain object within the same Module.

To be clear, the scope should grow the further away we go from VO:
VO < Entity < Aggregate Root < Domain Service < Application Service

If communication is needed between Modules, the relevant Application Services should talk to one another and not directly to the Domain objects.

Domain Events

A decoupled way for different parts of the system to indirectly interact with one another.
To use them you’ll need:

Publisher

There are at least two ways of approaching who should be in charge of event publishing:

  • Aggregate Roots publish changes in their state directly.
  • Aggregate Roots register events regarding them or their related entities.
    The application service is the one in charge of pulling the events from the Aggregate Root and publishing them.

No matter who you decide should publish the event, on the other side you’ll need someone to notify.

Subscriber

Event subscribers are very much like controllers, just limited in scope within our Domain.
They both ingest the primitive types of their respective input and use them to run the relevant use case.
Controllers receive requests, subscribers receive events, but in essence you can think of their role as equivalent in practice just with different scopes and implementation.

To put an example, a generic subscriber interface signature looks something like:

public interface DomainEventSubscriber<DomainEvent>

Concrete implementation looks like:

public class DoStuffOnCustomEvent implements DomainEventSubscriber<CustomEvent>

Where CustomEvent might, for example, implement or extend from DomainEvent.

Repositories

They abstract concerns about data storage and other infrastructure.
Ideally, there will be one Repository per Aggregate Root, and it should only be called by the relevant Application Service/s as part of a use case orchestration process.

They usually take the form of a domain leaning interface with concrete implementations based in the specific infrastructure at hand.
This is more or less borrowed from Ports and Adapters.

More to come

By now this is probably sounding like a big ball of jargon with not much of an architecture behind it, no real intention or plan.
We’ll go over that piece of the puzzle when we talk about the Strategic side if DDD.

Also read

https://thedomaindrivendesign.io/what-is-tactical-design/
https://thedomaindrivendesign.io/what-is-strategic-design/
http://gorodinski.com/blog/2013/03/11/the-two-sides-of-domain-driven-design/
https://herbertograca.com/2017/09/07/domain-driven-design/

Support the author with