Introduction
Event-Driven Architecture (EDA) emphasizes the identification, classification, and use of events in the architecture. When designing systems based on EDA, a recurring question is how to trigger commands across different sub-domains. This blog post explores one principle for dealing with these scenarios. This is one way to handle those scenarios, but not the only way.
Events and commands
An event is a statement of fact: something that has already happened. Events are named in the past tense, e.g., RoomCheckedOut or SaleRegistered. Once produced, they are immutable and cannot be retracted or changed, only superseded by a new event. The producer of an event does not know, and does not need to know, who is listening to it or what they will do with it.
This is different from a command, which is a request for something to happen, e.g., SendEmail. Commands are named in the imperative and are directed at a specific receiver who owns the decision of whether to carry it out. A command can be rejected, e.g., because the request is invalid. An event, on the other hand, describes something that has already happened and cannot be rejected. The receiver owns the domain rules and validation governing the command. These rules should not be duplicated in the calling applications.
As the name suggests, EDA is about having events drive the architecture. This means we talk with stakeholders and together try to identify different events. These are then documented and form the basis for defining our workflows, deciding which applications to build, and determining how we progress between workflow states.
Since events are such a central part of EDA, we need to have a clear understanding of the different event types and how we use them.
Event types
One way to classify events is to divide them into the following categories:
- Domain events
- Integration events
- Notification events
Domain Events
Domain events are used within one, and only one, bounded context (also referred to as a sub-domain or system in our architecture). They can contain private information or internal data references that are meaningful only within their sub-domain and should not be shared externally. They may require domain-specific knowledge to use their data correctly.
Domain events are one technique to decouple our applications in a given system.
Integration Events
Integration events are stripped of any internal information, and consumers shouldn’t have to implement domain-specific functionality to interpret them. The events should be as self-contained as possible and avoid lookups in the producers’ (sub)domain. Such lookups are a form of semantic coupling that we want to avoid. The producer of an integration event should not know about or depend on any external system to handle the event as part of their functionality.
We can use schemas to define the structure and types of integration events.
It can be tempting to merge two integration events with the same name from different sub-domains. They may even have the same data field names. However, that does not mean they will stay the same as we learn more about the sub-domains. Also, even though the field names and types are identical, this does not mean they have the same semantics.
Notification Events
Notification events are like integration events, but only stripped down to the bare minimum. They are typically used to collect statistics and notify other systems that something has happened without providing details.
Principle
Whenever an application A in one sub-domain depends on an application B in another sub-domain to perform an operation as part of its own workflow, we communicate using a command sent as an HTTP request. This gives application A direct feedback on whether the command was approved by application B. Application B is responsible for validating the command and enforcing the domain rules of its sub-domain. It may use events internally to carry out the operation, but this is hidden from application A, which only receives confirmation that application B has accepted responsibility for executing the operation.
Example
Let's say that we have a domain, HotelCompanyInc, with two different sub-domains, Hotels and Property. Each sub-domain has its own bounded context and ubiquitous language. For example, they could both use the term Sale, but with different meanings. In Hotels, it could mean that a room has been rented out, while in Property, it could mean that a property has been sold. They would then each have their own topic called Sales but prefixed with their sub-domain name, e.g., Hotels.Sales and Property.Sales. RBAC would ensure that only the correct sub-domain can publish to their topic.
Both sub-domains need to send out emails after a sale. HotelCompanyInc decides to create a new sub-domain called Common Services with an application that can send out emails.

There are a few temptations here, all of which violate the principle.
Violation 1: Create a Common Services library
In this scenario, we acknowledge that both sub-domains need to send out emails. One approach is to create a Common Services library that the other sub-domains use for the necessary validation and domain logic checks before sending out emails.

This violates the principle, as it would spread the domain rules across the sub-domains in the form of a library. Any update to the library is not necessarily picked up by the other sub-domains. There is no guarantee that the users of the library actually use it.
Violation 2: Common Services subscribing to both Sales events
This violation introduces an upstream dependency from the Common Services sub-domain to both Property and Hotels. This doesn't scale well because any new application that needs to send emails would need Common Services to start listening to its events. Any problems with any type of Sale will immediately and unjustly land on the Common Services team.

Violation 3: Other sub-domains add events to the Common Services topic
Here, Common Services listens to its own topic, which is good, but it is filled with events from other sub-domains. Common Services will indirectly be required to handle any typos or issues in the events produced by other sub-domains. We could introduce a library that other sub-domains must use to produce events, but then we will have the issues described in Violation 1. It is tempting to use an event schema to ensure valid events. However, a schema can validate the structure and certain constraints, but it cannot guarantee that the event correctly represents the intended business meaning.

Example solution
It is important to emphasize that we are talking about cross sub-domain command communication here. These violations do not necessarily apply to events within or between sub-domains.
For the example scenario, we would instead have an HTTP API in Common Services that the other sub-domains can call to send out emails. The endpoint would perform the necessary validation with potential synchronous calls to other external systems, ensuring the request is valid. Any invalid request will immediately get rejected, and the caller will receive a reason within the HTTP response.
If the request is valid, Common Services accepts responsibility for executing the operations and produces an event, e.g., CommonServices.EmailSendingRequested, which is placed on its own topic. Common Services will have an application in its system that listens to this topic and sends out the email.
As with any architecture, there are trade-offs. One downside with this approach is that other sub-domains would need to handle downtimes in the Common Services APIs.
One useful mindset is to imagine that the Common Services could, in theory, be a dedicated product with thousands of customers.

