Wait, what’s DDD again ?
This article requires familiarity with DDD concepts.
Let’s start with defining what DDD (domain driven design) is not:
- DDD is not a method
- DDD is not an architecture
- DDD is not a silver bullet
DDD manifests as a strategic approach towards the simplification of software development. It can achieve that by reducing complexity and dividing responsibility while employing strategic assets. To mention some: Ubiquitous Language, Aggregates, Domain Events, Bounded Contexts and so on.
If these concepts are not clear at all, the rest of the article will not make much sense.
DDD in different flavors
Having read Evans’ and Vernon’s books you are already familiar with Event Sourcing (henceforth ES). There are many advantages and many disadvantages as well. The increasing complexity of code can be beyond the requirements of a project.
Let’s see in what flavors we can have plain old DDD and compare it with ES following a simple example.
Our progress in reading a book is reflected in the Bounded context, and more in general in the Domain. It is a simplified example for the domain but it will be easy to grasp from a workflow perspective.
How to read a book with DDD
We can define limited scopes, use case scenarios and bounded contexts in a vanilla DDD approach. To ease the example let’s consider the three following scenarios:
I can move to next page. I can move to previous page. I can move to page X, where X can be any valid page number.
a sample code snippet that should be able to handle that for me would look as in:
We expect to be able to read, at any point, the page we are currently at. The current page number can thus be any positive integer value between one and the total length of the book. So a short Aggregate Root for the book entity would contain a constructor enforcing this case.
We already have a book representation with an identifier, a current page and the total number of pages element. Also a validating method if the next page we want to jump to exists in the book.
Now we can consider the most complex scenario: jumping to an arbitrary page in the book. The implementation can be quite straightforward given what we have already seen in the class snippet.
It is expected to validate the data coming in into the Aggregate. If a book contains only 105 pages, it won’t be possible to read page 108. Before completing the operation we need a check or it would move the Aggregate into an invalid state.
Other two methods, to move to a single page forward or backwards, can be implemented by wrapping the moveToPage method.
It is generally better to use self encapsulation whenever possible. This is a perfect scenario for this because it’s easy to abstract the next page number we want to focus on. This way we also reduce risks of having side effects.
Using direct access to fields can void the benefits of self-encapsulation. By managing an Aggregate in a single file we have less code to review and thus, this approach is less error prone. The last two methods can be quickly implemented as:
What book am I reading again?
Now we have a simple model on how to read a book and track our progress while reading it. We can move one page to the next, or skip forward, or even go back a chapter. When does Event Sourcing starts being different then?
The answer is simple in theory (only). A vanilla DDD implementation saves exclusively the last valid state of the Aggregate. Last valid state is computed by applying all the previous events registered. In Event Sourcing instead we save all those events that lead to the incremental evolution of the Aggregate. In Event Sourcing then, we can read all events but one an get, for instance, the previous valid state. In a vanilla DDD implementation that would mean saving all the previous states separately.
By starting to read the book and persisting it to the repository over and over, with operations like:
Clearly the current page number is the last one persisted through the repository (i.e. 4). And in no case we can be sure of how we got there, the sample shown up until now, in fact, has skipped page 2.
Naturally, we can add more code and devise a neat strategy to address that issue. We could start tracking the previous page and add a comparison step; if the distance is more than one page, we are heading too far. Or we could add a completely separate page tracking mechanism to show which pages we’ve already visited.
Or again, we could move to Event Sourcing and start saving every simple valid operation that has happened:
We have now saved two simple events. They represent the whole composable status of the current BookAggregate instance. On top of that we have all the historical audit trail that lead to the current status.
What would these events look like then? Here’s a first guess:
Given these simple set of events it’s easy to figure out if page was skipped. We could also answer questions like: “what page was I reading on 5th January?”, “Had I read page 24 on a given date?” or “How many pages on average do I read in a week?”.
This higher flexibility comes at a cost, and it is not cheap.