A brief introduction to domain-driven design (DDD) and event-driven architectures (EDAs)

What is domain-driven design (DDD)?

A domain is the specific and bounded real-life context in which a problem exists and unfolds. It is a sphere of knowledge around an activity. In the case of an agrochemical business, e.g., a domain expert would deeply understand the agrochemical industry and the business products, they would possess the technical know-how related to the specific business processes and its equipment.

When engineering software, whether it's a green or a brownfield project there's always a gap to bridge between the software experts and the domain experts. They both speak in different languages, think in different terms and manage different concepts. When designing software in a domain-driven fashion we acknowledge the importance of language in this bridge-building process and therefore attempt to work with a clean design that follows a shared logic with the domain experts.

The only way of developing this shared logic between us and the domain experts is by studying the domain as they did when they first decided to acquire that specific expertise. As we study the domain we'll build a mind model 🧠, and it's precisely this model that will serve as the founding basis for our entire software system.

Domain modeling

A model is a system of abstractions representing selected aspects of the domain. Later on, our software will build up on top of this system of abstractions. When shaping our domain model we must refrain from trying to mimic reality. In the words of Eric Evans: realism is a distraction, meaning it doesn't give us a useful model. Realism is a nuisance since the only goal of a model is to serve us some use.

It may not be obvious, but usefulness and specificity go hand in hand. For our domain model to be useful, we need to get very specific to the problem we are facing and refrain from thinking about generalizing or importing solutions from different problems, even if the domains appear similar.

Through this modeling we'll better understand the domain and reduce critical complexity and overhead from our software. During this process we need to be intentional about the language we bring into the project's life. I find Evan's concept of ubiquitous language concise and sticky:

Ubiquitous language is the language structured around the domain model and used by all the team to connect its activities with the software.

When this ubiquity is taken even further, software engineers develop what is known in domain-driven design as a DSL (domain-specific language).

(TL;DR) Wrapping up DDD:

Being aware of the ubiquitousness of the language around our model helps bridging the gap between domain and software experts, and making the model as specific as possible is key in paving our software's way to ✨success✨.

What are event-driven architectures (EDA)?

Before getting into architectures, let's define events. An event is an occurrence in the system, it's a change in the state of any given component or object of the system. The event is not the thing that changes, but the change itself. It's very important to note the difference between events and commands.

Since the event is the change itself, it's subsequently treated as an object that can be passed around between systems. Its occurrence implies no consequence whatsoever whereas a command is an imperative consequence, it's an order to do something.

Back to architecting. EDAs date back to Ruby and its eventmachine. However, evented programming wasn't popular until the rise of Node.js. Nowadays EDA means different things to different teams and communities. In an effort to conciliate all these practices and after talking to many event-driven devs, Martin Fowler and his peers came to the conclusion that every EDA falls into one of these four concrete architectures:

1. Event notification

When an event occurs, it is encapsulated as a self-contained object that can be then consumed by different components of different systems. The consumers get notified, after they receive a message they decide what to do with the state change.

⚖️ Pros/cons:

This architecture decouples the receiver/consumer from the sender. However, it can get difficult to see the big picture of all the systems and understand the overall behaviour.

2. Event-carried state transfer

It works like an event notification architecture until the receivers/consumers decide what to command as a consequence of the state change. When using event notifications many consumers might want to access the sender to get the details of the state change thus creating a surge of traffic. When we carry the details of the state within the notification, we reduce load for the sender/supplier.

⚖️ Pros/cons:

We reduce the load on the sender side. This creates a new challenge: now the data is replicated, and we need to strategize for consistency (or get comfortable working with eventual consistency).

3. Event sourcing

Of course you know git… Well, event sourcing is conceptually very similar to version control. Our systems and their components experience state changes, and in a parallel data structure we keep these events logged.

⚖️ Pros/cons:

It is great for auditing and debugging compared to the previous architectures, and it allows us to keep alternate states or “branch” between states. However, it is a very unfamiliar architecture and both the event schema and the versioning can get messy and hard to work with.

4. CQRS

CQRS stands for command query responsibility segregation. This architecture is useful for systems that update much less frequently than they read. Inside the system we build two separate, isolated components: a query model and a command model.

  1. The query model reads (and you can have different ways of reading).
  2. The command model updates (doing the complicated calculations required by the business logic for every state change).

⚖️ Pros/cons:

Very useful for reporting purposes, where the operational database needs to be treated before conveying meaning.

And there's a fifth EDA I found besides the four identified by Frowler: SEDA.

5. SEDA

SEDA is a staged event-driven architecture, where every stage is a self-contained unit consisting of the event handler, an incoming event queue and a thread pool. This way events are decoupled from one another and can be passed around and consumed between different components of our system. The goal of this architecture is to improve EDAs performance and latency. Here's a paper on SEDA if you feel like digging into it.

Differences (I found browsing Stack Exchange) between EDA and…

The observer pattern

This pattern commands the observer to do something (with one of the observer's methods), when the state of the observed changes. This is a synchronous flow. In an EDA, on the other hand, the state change is just delivered as a message. This is (in some cases) done asynchronously. In the observer pattern, the observed keeps references of its observers, which is not the case in an EDA.

Reactive programming

With a reactive approach you have a pull strategy: you only declare to watch and react to a change in data when you need to. Whereas in EDA you have a push strategy: the event is pushed and some other components of code may or may not use it.

How do domain-driven design (DDD) and event-driven architectures (EDA) converge?

  • The first and most important way I think these two driving spirits converge is that both build on top of a very old principle: keep the updating state as far away as possible from the business logic!
  • In EDAs we have bounded systems that message each other events, and then do (or don't) whatever they need with that notification. These bounded systems, if designed in a domain-driven way, will manage ubiquitous languages. Pushing this concept forward, we could even build digital twins!
  • DDD is an attitude to look forward to when starting a new project or taking over an existing one, and keeping EDAs in mind will help us make our architecting process agile and our software system foundations robust.

Hope this high-level intro helps you recollect your thoughts and organize your ideas prior to your next big project. Cheers! 🦾

Sources