Dieser Blogpost ist auch auf Deutsch verfügbar
TL;DR
- Differing understanding: The meaning and motivation behind “Domain Event”, “ECST”, and “ECST Events” are often understood differently and used imprecisely.
- ECST is a property of events, not a distinct event type: It only says whether an event carries all of its data itself. Its counterpart is the Event Notification, not the Domain Event.
- Two independent axes: Business trigger vs. state in the event is something different from ECST vs. Event Notification, or ECST vs. Domain Event. That’s why “ECST Event” is a misleading term.
- A better name, “Replication Event”: It only replicates external data, without a business reason.
- Rule of thumb: Domain Event = react to a business event; Replication Event = keep external data locally to increase autonomy.
As a software architect, DDD practitioner, and consultant, I usually work with my clients on complex business systems that are modularized along business lines with Domain-Driven Design and implemented as Self-Contained Systems. In this context, in reviews of architecture concepts, in workshops on modularization and integration, and in discussions in general, I repeatedly see architects and developers using terms from the realm of DDD and Event-Driven Architecture without having a shared understanding of them.
You could accuse these people of carelessness, but sometimes a shared understanding is difficult precisely because concepts and terms are ambiguous and everyday usage in some contexts has drifted away from the original definition. Or because the original motivation for the emergence and use of specific concepts is no longer known.
In my opinion, this is exactly what applies to Domain Events, ECST (Event-Carried State Transfer), and the term “ECST Event” that is so common in practice. This article is about exactly these terms and their clarification (at least from my perspective).
Domain Events
Domain Events are the central concept of Domain-Driven Design (although one introduced only in hindsight) for analyzing the business domain, for example via Event Storming, for explicitly modeling this domain in the Domain Model of a Bounded Context, and also for integrating Bounded Contexts with one another. Domain Events describe the domain from a dynamic perspective (instead of focusing on the static data view), and each Domain Event represents the result of fulfilling a specific business responsibility within a Bounded Context. A “properly” modeled Domain Event has well-defined business semantics and lets the consumer understand what happened at the producer of the Domain Event, so that the consumer can react correctly to the Domain Event within the scope of its own domain, both within the same Bounded Context and across Bounded Context boundaries (“whenever a customer has been successfully registered in the ‘Registration’ Bounded Context, a wallet with an initial balance must be opened in the ‘Accounting’ Bounded Context”).
“ECST Events”
In contrast, there are events that communicate only the state that has newly arisen or changed within a Bounded Context as a result of the domain logic (for example the state of the “Customer” aggregate after successful registration). The business reason for the change is not communicated, only the result after the change.
In practice (at least as I perceive it), such events are often called “ECST Events” to set them apart from Domain Events. The term “ECST Event” is problematic in at least two respects: first, it was not originally defined this way, and second, the term communicates very poorly what purpose this kind of event serves.
ECST according to Fowler
The term ECST was described by Martin Fowler in 2017 in [1], in essence as:
ECST: an event carries all relevant state data directly with it, so that consumers can update their local dataset on their own, without having to query the source system again.
So the concept describes an event that already carries within itself all the state needed for the consumer to process the event (which state this is in a specific case is an aspect of modeling, or Context Mapping, and is outside the scope of this article). The consumer therefore does not have to go back to the producer of the event to request additional data.
This not only makes the event more self-contained (the event becomes more “self-describing”), but above all also allows a temporal decoupling of the consumer from the producer at the time the event is processed (the consumer can process the event without the producer having to be available). When a consumer receives an ECST-based “customer registered” event, the event already contains the entire customer state relevant to the consumer, and specifically as it was at the moment the event arose at the producer. If the customer’s state has already changed between the creation of the event and its processing at the consumer (for example because the customer was blocked shortly after registration), this does not change the state in the event, but (possibly) produces a second event, “customer blocked”, again with the relevant customer state.
Event Notification according to Fowler
In contrast, in the same article Fowler describes the concept of the Event Notification, which is used only to inform consumers that a change has occurred at the producer, without delivering much context about the change itself as part of the Event Notification. In practice, typically only a reference (an id) to the changed state is included, which a consumer can use to request more data if needed. An Event Notification itself thus describes a narrower contract between producer and consumer on the level of the event, but it results in a temporal coupling at the time the event is processed (the producer must be available at the same time as the consumer so that the consumer can request the desired data) and an additional contract (including separate versioning, authentication, authorization) between producer and consumer, if the consumer needs to request additional state from the producer in order to process the Event Notification.
It also has to be kept in mind that a producer is not automatically able to deliver this state exactly as it existed at the producer at the moment the Event Notification was created. Often, naive implementations deliver the current state of the referenced object to the consumer. So if a producer publishes two Event Notifications, “customer registered” and “customer blocked”, each with the customer number as a reference, but the consumer requests the customer data as a result of processing “customer registered” only after the customer has already been blocked at the producer, then - without further precautions at the producer - the consumer will get the state of the already-blocked customer. Depending on the consumer’s domain logic, this may be fine, but it can also cause confusion if the consumer assumes, for example, that a just-registered customer must be in the state “active” rather than “blocked”.
In practice, using Event Notification (instead of ECST) is advisable when the event’s state would be very large (and would thus exceed the limitations of the infrastructure, for example), when the state is very short-lived (changes very quickly), or when parts of the state must not be accessible to all consumers and therefore specific authorization rules should be applied when the consumer requests the state.
ECST vs. “ECST Event”
The crucial point here: ECST is not the counterpart to Domain Event. ECST is the counterpart to Event Notification. ECST vs. Event Notification is a dimension that is orthogonal to Domain Events vs. “ECST Event”. Both Domain Events and “ECST Events” can be based on ECST, or can be realized as an Event Notification. This clearly highlights the conflict in the meaning of ECST and “ECST Event”.
Depicting this as a matrix of state in the event vs. business trigger in the event shows that ECST is understood both as a dimension on the “state in the event” axis and as a concrete event type.
To resolve this conflict, it helps to consider the motivation for using Domain Events vs. “ECST Events” in the first place:
-
Domain Events are an enormously valuable concept for explicitly expressing business events within a Domain Model. They are also a valuable means of representing processes that span multiple Bounded Contexts end to end.
This need arises because we typically have to map the business complexity from the problem space onto different Bounded Contexts in the solution space, in order to implement them as autonomously as possible as subsystems, with suitable, locally consistent Domain Models and by dedicated teams. Here, Domain Events help to integrate Bounded Contexts via business events.
-
“ECST Events” are a means of replicating data from an external system without having to know its domain logic in detail. Thanks to the replication, the external data is already available in your own system when it is needed to carry out your own domain logic, and does not first have to be obtained from the external system via a synchronous integration at that moment.
This need arises from the desire to increase the autonomy of individual subsystems and to realize them as Self-Contained Systems. Autonomy is increased by reducing the temporal coupling between subsystems at the time business logic is executed.
This perspective allows us to give “ECST Events” a name that better describes their nature and their purpose: Replication Event.
Replication Events
Replication Events exist to replicate data from one subsystem to one (or more) other subsystems. The name Replication Event focuses on the purpose of the event. The consumer needs only a replica of the data (of an entity) to increase autonomy, and not to react to a business event from an external Bounded Context. Accordingly, a Replication Event also communicates no business reason for the change to the contained data, only the data itself.
The “Event” part of the name is actually debatable, since it is really just a message and not an actual event. But if a Replication Event is given the semantics of “entity updated” or “entity deleted”, then “Event” fits again.
The matrix now looks like this:
Concrete examples for the different quadrants in the matrix could be:
| Domain Event | Replication Event | |
|---|---|---|
| ECST | “customer registered” with all relevant customer data | Type “updated” (or “deleted”) with all relevant customer data |
| Event Notification | “customer registered” with only the customer number; the consumer authorizes itself for access to sensitive customer data when retrieving via the customer number | Type “updated” (or “deleted”) with only the customer number; the consumer authorizes itself for access to sensitive customer data when retrieving via the customer number (*) |
(*) Replication Events realized as an Event Notification of course lead to a temporal coupling at the time the Replication Event is processed (the building of the replica), but using Replication Events as such avoids the temporal coupling at the time the business logic that depends on the external system’s data is executed (the use of the replica).
Domain Events vs. Replication Events
After resolving the naming conflict, the question remains of when which event type is the right one. The following heuristic can help:
Domain Events are the right concept when a Bounded Context is supposed to react to a business event from an external Bounded Context (“whenever a customer has registered, a wallet must be opened”). It allows the consumer to react to the specific business event without needing to know any further details of the specific domain logic.
Replication Events are the right concept when a Bounded Context, in carrying out its own domain logic, needs only the state that is owned by an external Bounded Context (provided a high degree of autonomy is desired). Here, data replication is the actual goal.
Domain Events can also be used to build a replica at the consumer based on the sequence of Domain Events, provided the Domain Events each contain the state necessary for the consumer’s view of the replica.
However, this requires a projection of business events onto states (it must be decided, for example, which business event should cause an entity to be deleted from the replica), and it leads to tighter semantic coupling between producer and consumer, because the consumer has to understand the relevant Domain Events just to build a replica. If a producer adds another Domain Event to its Domain Model, this also has to be recognized and processed at the consumer in order to keep its replica up to date. In such a case, it is advisable to enable building the replica via Replication Events.
- No business events should be derived at the consumer from a Replication Event (or from the sequence of several Replication Events), for example deriving the business event “customer blocked” from the change of a customer’s “status” property from “active” to “blocked” from two Replication Events on the consumer side. The reason is that the consumer then has to speculate about the business trigger while knowing only its effects on the state (the business trigger for the “blocked” status could, after all, also be a failed identity verification, for instance). In such a case it is semantically better to publish the business event explicitly as a Domain Event (for example “customer blocked”), so that a consumer can react to it explicitly.
Expressed in the language of Event Storming [2]:
- If there is a rule (“policy”) in the Domain Model that processes an incoming event and triggers a business action (“command”) from it, which is then processed by an aggregate, then the event must be a Domain Event.
- Whenever external state (a kind of “read model”) is necessary to evaluate the rule, it could or should have been made available beforehand as a replica via Replication Events.
This also makes clear: a Domain Event can be part of the Domain Model, whereas a Replication Event does not manifest directly in the Domain Model.
As a consequence: if a distributed system is not to be built on the ideas of Self-Contained Systems and autonomy is not required, the need for Replication Events vanishes. The external system’s data needed to carry out the domain logic is then obtained directly at that moment via synchronous integration (for example via a REST interface). The need for Domain Events, however, remains: it does not arise from the need for autonomy, but from the fact that, due to the modularization of the domain in the solution space, business processes can span multiple Bounded Contexts.
Outlook
How Domain Events and Replication Events can concretely be modeled (within an Onion Architecture) and mapped onto suitable infrastructure, and what options for combining them exist, will be the subject of a future article.