The opening
It’s been almost a month and a half since the last article in this series was published. Well, I trust ðŸ˜. It was already March, the season of cherry blossom 🌸, and the series had to be relaunched. During the break, I will receive your messages and questions about DDD from time to time. I will reply to your questions once I have time. Thank you for supporting this series of articles 😄.
An overview of the
In practice domain-driven design (DDD), we often encounter situations where objects in multiple domains interact with each other. For example, aggregation root A needs some signal (or some data) from aggregation root B before performing some operation. In a singleton application, there are conditions and opportunities for both to be strongly referenced to complete the operation, but this directly breaks the norm of domain-driven design, leaving the project out of control and back to the big ball of mud.
Now, we can take a cleaner approach to such problems and make it clearer to describe the activity signs of domain objects. That’s our topic for today ———— “Domain Events”. So what exactly are domain events? What benefits will the introduction of domain events bring to our existing DDD projects? Do you have to use domain events? This article will take a fresh look at the concept of “domain events” from a different perspective and present code snippeds (the code snippeds in this tutorial are all in C#, with the idea of 😀 spanning any programming language).
What are domain events
In the original book, Domain-Driven Design: Solutions to Software Core Complexity, there is no direct mention of domain events. Domain objects were put forward by author Evans in the later stage, and the current version of domain events has been developed through the continuous practice and evolution of experts such as Udi Dahan (author of Nservicebus) and Jimmy Bogard (author of MetdiaR and AutoMapper).
Here IS an excerpt from the description of domain events in the book Implementing Domain-Driven Design:
Events occurring in the domain that are of interest to the domain expert. Activities occurring in a field are modeled as a series of discrete events. Each event is represented by a domain object, which is part of the domain model and represents what happens in the domain.
How are domain events used
When you first see the word “event,” you might immediately associate the event in C#, the delegation-based event. Indeed, there are commonalities, such as: “When an event occurs, all objects associated with that event are affected.” So, if you know events in C#, it will help you understand “domain events” better.
From this we can deduce that in the process of domain-driven design modeling, if an action is found to have occurred, other domain objects associated with it will be affected. Then the action could be a “domain event.”
Light is a bit confusing conceptually, but let’s look at a practical example: “When the user adds an item to the cart, the suggested item below will suggest the same item to him.” This is a classic example of contextual relationships, where the addition of an item to the cart triggers recommendations for similar items. So let’s take a closer look at the process and catch the key words inside. “Item added to shopping cart” leads to “recommendation of similar items”. Is it similar to the description in the paragraph above? So after a closer look, we can capture a domain object, which you might name (ProductAddedEvent).
Why do we name it the past tense? This also confirms the opening sentence “after the action has taken place”. When the event is captured, the event information is passed to the “Recommended Goods” aggregation root and the appropriate processing logic is performed.
So what is the source of the incident? “User click”, “page response” these are not oh! Remember, we care deeply about the domain object, which obviously has nothing to do with our domain object. So we can naturally turn our attention to the “shopping cart”, which may be an aggregation root, which has an action called “add item”, and when this action is completed, an event “add item complete” is raised.
We might end up with a process like this:
So you can see that domain events serve both to describe domain information and to undertake interactions between different aggregation roots. Of course, there may not be only one event, and there may not be only one domain object affected. Just as the “recommended item” receives the “finished item” event, it can itself generate another domain event to pass downstream.
Conversion of thinking
At this point, you may feel that using domain events is not quite the same as capturing other objects, such as value objects, entities, and so on. Because it can be “implicit” to a domain event, we have no intuitive sense of its existence.
So think carefully about this: when you use domain events, you will agree that your project needs to be event-centric. Each domain object in the project will complete a series of interactive processes by generating and publishing domain events.
Here’s an excerpt from Domain-driven Design Patterns, Principles, and Practices: “Domain events will be revealed in a knowledge refining session with domain experts. Revealing domain events is so valuable that DDD practitioners have innovative knowledge refining techniques to practice in order to become more focused on events, such as event storms. However, using these innovative technologies brings new challenges. Since the conceptual model is event-centric, the code needs to be event-centric so that it can express the conceptual model. This is the value of the domain event design pattern.”
So most of the time you will feel the project moving towards EDA (event-driven architecture) style. At this point, you might think of another pattern in DDD: EventSource, and think you must use EventSource to build your DDD project. This is not necessarily true. There is no direct correlation between using domain events and using event tracing, although domain events can help event tracing work better.
Capturing domain events
With the above introduction, you probably already have a feel for the discovery of domain events. When aggregations interact with one another, we often find that there is some domain event between them that triggers this set of behaviors.
If you talk to a domain expert, you find the key words: “when ………………” If A is finished, ………… , “take place ………… When. These terms may implicitly tell you that there may be a “domain event” object.
Internal events and external events
Before using domain events, it is important to know that events are actually divided into “internal” and “external.” As it is described, internal domain events occur within the boundary, while external events occur outside the boundary (for example, microservice A generates an event, and microservice B is affected by that event).
In Microsoft’s guide to the ESHOP case,.net Microservices – Architecture, this is called “Domain events and Integration Events” :
The diagram also graphically illustrates how internal events interact based on a boundary:
External events often require some infrastructure for inter-process and distributed communication between remote services, such as rabbitMQ, Kafka, etc. This article focuses on internal domain events, which will be covered later in the Domain-Driven Design in Distributed series.
Optional Or required
Does my DDD project have to use “domain events”? Perhaps you have never seen this question on the Internet, so there is no definitive answer to this question. Personally, I think the answer is “not necessarily”.
As mentioned above, if you start using domain events, then your project and thinking will shift to “event-centric.” Most interactions in the realm will be presented in the form of events. So instead of thinking about “my DDD project must use” domain events, “think about” do I need events as the center of my thinking?” .
So the answer to that question is up to you. This is one of the reasons why you may not find “domain events” in some DDD frameworks or DDD projects.
So how do aggregations interact with one another without using events to model them? Please look at the ↓ below.
Domain events VS domain services
I did a lot of searching with the search engines and couldn’t find any comparisons between domain events and domain services. But I think there are a lot of similarities. When Evans first came up with the concept of domain-driven, domain events were not considered, which meant that we could do domain modeling and business processes from the original domain objects.
Going back to the question, can you only do events between aggregates? Not necessarily. Domain services are also responsible for transforming domain objects into domain objects.
Let’s review some of what we learned in the domain Services section:
When we find that an operation cannot assign an entity or value object and that the operation is important to the business process, we often need to use domain services to get A C through A and B. A needs A tedious internal strategy to get A result B. (PS: A,B,C refer to value objects or entities in domain objects)
So this also means that multiple domain objects, such as aggregate roots, can be manipulated internally by domain services. So some DDD frameworks use domain services as the primary tool for process operations, allowing consumers to inject multiple repositories into domain services to operate on multiple aggregate roots.
Domain events, on the other hand, publish domain events to interact with different domain objects.
Should you use “domain services” or “domain events”? Start by asking yourself whether you need to introduce an event model. If yes, use domain events as a priority.
These are two objects that can easily make people dizzy, and I will give you a sense of how they are used in two sentences:
A: In the case that the express needs to be checked when it is put into storage, such as whether it is overweight or not, we have not introduced other field objects except the aggregation root of “express”. So the “check” operation here, who should the behavior go to? To “express”? Express check yourself? Obviously not, so when something doesn’t belong to an entity or value object, we need to introduce a domain service.
B: When the delivery is delivered to the store, it proves that the delivery has arrived, and the deliveryman will call the user to deliver it. In this scenario, we have found domain objects such as “Courier”, “point of business”, “Courier”, etc. What would we do if we were to complete a “Courier arrival” use case? Call “place of business” to “take delivery”, and then call “delivery Courier” to “deliver delivery”. Interactions between multiple aggregation roots are involved here, so domain services or domain events? If you model based on events, you can use domain events; otherwise, you can use domain services.
If you are starting a DDD project, I recommend that you use event modeling first. That is, consider using domain events. Interactions between aggregate roots and aggregate roots are communicated through domain events, leaving the policy calculations of domain objects to domain services. A clearer division of responsibilities between the two.
practices
The practical scheme mainly adopts the domain event implementation scheme proposed by Jimmy Bogard. The aggregation root holds a collection of domain events that are assigned to the corresponding processing events through the event dispatcher.
So we can start by establishing several interfaces: IDomainEvent(indicating that the class is a domain event), IDomainEventHandler(for intercepting and processing domain events), and IEventDispatcher (for distributing domain events to handlers).
public interface IDomainEvent
{
}
public interface IDomainEventHandler<in TDomainEvent>
where TDomainEvent : IDomainEvent
{
Task HandleAysnc(TDomainEvent domainEvent, CancellationToken cancellationToken = default);
}
public interface IEventDispatcher
{
Task DispatchAsync<TDomainEvent>(
TDomainEvent domainEvent,
CancellationToken cancellationToken = default) where TDomainEvent :IDomainEvent;
}
Copy the code
Then you need to add some methods to the aggregate root so that it can keep domain events in the instance:
public abstract class AggregateRoot<TKey>
{
public virtual TKey Id { get; set; }
protected List<IDomainEvent> _domainEvents = new List<IDomainEvent>();
public virtual void AddDomainEvent(IDomainEvent domainEvent)
=> _domainEvents.Add(domainEvent);
public virtual void RemoveDomainEvent(IDomainEvent domainEvent)
=> _domainEvents.Remove(domainEvent);
public List<IDomainEvent> GetDomainEvents()
=> _domainEvents;
}
Copy the code
Finally, domain events held on aggregate root instances are distributed to the corresponding event handlers through the event dispatcher before the repository is persisted:
// EF Core DbContext
public class OrderingContext : DbContext
{
public async Task<bool> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
{
//Get aggregateRoot
var aggregateRoots = dbContext.ChangeTracker.Entries().ToList();
// Dispatch Domain Events collection.
await _eventDispatcher.DispatchAsync(aggregateRoots,cancellationToken);
// After this line runs, all the changes (from the Command Handler and Domain
// event handlers) performed through the DbContext will be committed
var result = await base.SaveChangesAsync(); }}Copy the code
Due to the limited space, the above implementation scheme only gives you an idea, so there is a lack of some implementation, if you need to contact me, I will extract a small Demo and upload it to Github.
For an alternative implementation, you can check out the Microsoft Eshop tutorial.
Why domain events
Why would I suggest that you prioritize using domain events? In order to make it easier to disassemble projects for micro services. Let’s say we all use domain services to do the interaction between aggregate roots. For example, we now have A domain service A that needs to help aggregate root A and aggregate root B complete the operation:
public class DomainServiceA
{
DomainServiceA(IRepositoryA repositoryA,IRepositoryB repositoryB);
}
Copy the code
In this domain service, there are repositories for aggregation roots A and B. Now A and B are in the same service, which works fine. But what if one day, B needs to be spun off as a separate service? The domain service had to be changed.
When aggregation B is split out, if B needs an event published by A, then B only needs to add A type of this event to its own project without modifying other logic. Internal events may need to be converted to external events, but the core business code does not change.
So at the beginning of the construction project, we need to take the long term into consideration when selecting the type.
conclusion
This time we introduced domain events in domain-driven design. “How to capture domain events?” “, “Does DDD necessarily need domain events?” Trust these questions, and you have your own answers in mind.
Domain events can help us better describe the state between objects in the domain, just as the point mentioned at the beginning of this article: “If an action is detected, other domain objects associated with it will be affected.” Modeling these abstractions as domain events will be of great benefit to your project.
Attachment: code for this section
Every time I talk about this series, I feel more serious. If you prefer something lighter, you can pay attention to my other series “Five minutes of.NET”.
Finally, secretly say: creation is not easy, point a recommendation…..