Strictly speaking, event-driven is not a pattern, it should be an architectural style or programming paradigm. However, event-driven in domain-driven design doesn’t cover that much of a landscape and tends to be part of an overall system solution, so I’ll put it in the category of patterns.
Events are familiar and easy to understand concepts for both business people and developers, making them a useful tool for modeling domains, whether in daily requirements communication or system design. In the analysis method of event storm, domain event is an indispensable element. Before moving on to events in domain-driven design, let’s take a look at why the “event” pattern is used.
Why “Domain events” are needed
As mentioned in the previous article on Aggregation Aggregation, one of the salient features or limitations of Aggregation is that only one Aggregation should be updated per transaction. Undoubtedly, this poses a great challenge to system design. It is not easy to design a Aggregation with moderate granularity that meets business requirements. But domain events provide us with a more elegant solution, generating a new event after the Aggregation has updated and broadcasting it out, with other aggregations that subscribe to the event updating the other aggregations. This uncoupling the Aggregation.
Another area where domain events come into their own is the interaction between different bounded contexts. A popular architectural style is to treat different bounded contexts as different microservices that interact with each other through an API. However, API is not the only solution. In some scenarios, the event model based on message-oriented middleware can better reduce coupling and improve system resilience.
How to use Domain Events
While the concept of events is well understood by developers, the event-driven pattern is rarely used in real projects. This is partly due to the lack of framework support for event-driven models, which often require manual handling of a lot of work including exceptions, sequential sending, and so on. Another reason is that the event-driven programming model is very different from the sequential programming model. After the event is issued, the control logic of the program is handed over to the subscribers of the event, which is not so intuitive and convenient in the development and troubleshooting. So what follows is my hands-on experience with event-driven models that I hope will help you.
Model domain events
The domain event is an object, so it also needs to be modeled and its data structure defined. Before we start defining our domain events, let’s introduce the business scenarios.
When an insurance claim application is submitted, after a series of process review and confirmation of the claim amount and other data is correct, special personnel will conduct the final second approval. If the approval is approved, the related expenses can be paid to the insurance beneficiary. From the business point of view, after the approval of claims, there will be a series of background business operations, the first is the production of financial expenses and related vouchers, and then the generation and sending of claims notice. If the policy is terminated due to claims, further operations need to be carried out on the policy. These business actions undoubtedly introduce several Aggregation objects, and certainly cannot be done within a transaction with a single Aggregation, so domain events must be introduced. Here is the business to event diagram:
The domain event is generated by Aggregation, which in our scenario is the ClaimCase object. The format of the domain event name is generally the name of Aggregation that generated the event + the past tense of the verb that generated the event. Here, the action that generated the event is “approve”, so we can name the domain event as “ClaimCaseApproved”. This is actually the same advice as the event storm.
With the name settled, let’s take a look at the data structure inside the event. The internal data structure of ClaimCaseApproved is generally similar to the Aggragation that generated it, both of which are relatively important domain object data. In our business scenario, there will be the case number of claim settlement, insurance policy number, accident date, etc. Note that for domain events, it is common to add two additional attributes, one for the event’s date of occurrence and one for the event’s unique number. Both are required for troubleshooting and debugging of problems, as well as processing by subscribing to events. Here is sample code for the event:
public class ClaimCaseApproved {
private String eventId;
private LocalDateTime occuredOn;
private long claimCaseId;
private long policyId;
private LocalDateTime accidentDate;
...
}
Copy the code
Event generation, sending, and subscription
With the data model in place, we need to consider where event related code should be placed in a hierarchical architecture. So far, there is no uniform rule, so I will introduce the methods that have been tried in previous projects, some of which are good and some of which are not convenient, and I will leave it up to you to choose which ones.
One approach is to handle the event send and subscribe logic in the domain service, with the event generation being handled by the domain object, Aggregation. Let’s take a look at the sample code:
public class ClaimCase {
public ClaimCaseApproved approve(a) {
...
}
}
Copy the code
The code here is simple, ClaimCase is a Aggregation domain object, and the Approve method performs the business logic of approval, and its return is the event it produces. Now look at the code for the domain service:
public class ClaimCaseService {
private DomainEventPublisher publisher;
...
public void approve(a) {
ClaimCase claimCase = ..... ;
ClaimCaseApproved claimCaseApproved = claimCase.approve();
publisher.publish(claimCaseApproved);
...
}
}
Copy the code
The domain service ClaimCaseService calls the Approve method of the domain object to get the generated domain event and send it, where DomainEventPublisher is just an interface whose implementation depends on the infrastructure layer. The practice field of the problem is the need to object explicit return the event object, if your domain object this method just need the return value, and Java is a language does not support multiple return values, then some awkward and relatively straightforward solution is to introduce a third-party library, return a similar to the data structure of a Tuple.
Another alternative to event generation is to keep a data structure inside the domain object to store the generated events, and then call a specific method in the domain service to fetch the generated events and send them. The code for this example is as follows:
public class ClaimCase implements DomainEventGenerator {
private Map<DomainEventType, List<DomainEvent>> registeredDomainEvents;
public void approve(a) {
...
registerDomainEvent(new ClaimCaseApproved());
}
}
Copy the code
Instead of returning the corresponding domain event, the ClaimCase method saves the event in an internal Map. Take a look at the changes to ClaimCaseService:
public class ClaimCaseService {
public void approve(a) {
…
claimCase.approve();
Map<DomainEventType, List<DomainEvent>> registeredDomainEvents = claimCase.getRegisteredDomainEvents();
publisher.publish(registeredDomainEvents);
}
}
Copy the code
Field service ClaimCaseService after calling the claimCase approve method, the explicit calls claimCase. GetRegisteredDomainEvents method, get registered domain object internal field events, and then send.
The other is the sending of events, and the processing logic is placed in the Application Service layer, namely Application Service. The details are much the same as in the domain Service. I won’t go into details here. But there are a few things to keep in mind:
- Domain events are part of the domain logic, so you should not rely on some underlying framework or middleware at the domain level, such as an API that directly relies on a message-oriented middleware.
- Events should be sent asynchronously and non-blocking, and should not block the thread currently being processed.
- Design to avoid the occurrence of event chains, that is, the processing of one event leads to another event, and the processing of the second event leads to a third event, which will become a ring without the design noticing. (Don’t ask how I know ~~~)
- Consider the ultimate consistent solution, keeping track of logs, and handling and troubleshooting of lost events.
Using a framework
Either way, you probably need to implement the entire event-driven architecture through a pattern like “Observer,” but if you’re Java, you can use a framework like Spring to take subscription and publishing of events out of the domain model through dependency injection. Let’s see how to implement the previous example using Spring:
public class ClaimCaseApproved extends ApplicationEvent {
...
}
Copy the code
Our domain events don’t change much, except that we inherit the ApplicationEvent base class provided by Spring. ClaimCaseService can inject Spring’s ApplicationPublisher via Spring:
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
Copy the code
Subscriber is called Listerner in Spring, here we can define our own Subscriber:
@Component
public class FinanceFeeSubscriber {
@Autowared
private FinanceClaimFeeApplicationService financeClaimFeeApplicationService;
@Async
@EventListener
public void handleClaimCaseApproved(ClaimCaseApproved event) {
FinanceClaimFeeApplicationService. GenerateClaimFeeFor (...). ;
...
}
}
Copy the code
The code above we use Spring’s ability of application layer service FinanceClaimFeeApplicationService injected charge module, Then, the asynchronous method for processing domain events is declared through @async and @EventListener, two annotations. In the method, the application layer service is called to complete the processing logic related to expenses caused by the approval of claims.
The important thing here is to declare the method asynchronous so that the method that handles the event runs in a separate thread and does not block the thread that issued the event. Secondly, there is no requirement on the sequence of event processing in the business, so it can be processed in parallel. According to the above code, two subscribers can be created, which are responsible for the generation of claim notice and business processing of policy termination, without affecting each other.
If you don’t want to use the event mechanism Spring provides, consider EventBus from Google Gauva, which provides similar functionality and is very simple to use.
Finally, regardless of whether you use Spring or Guava, event data is stored in memory and unprocessed data is likely to be lost in the event of a service restart, so it’s important to log and figure out how to handle event loss, manually triggering reoccurrences if necessary.
summary
Event-driven is an architectural pattern that fits well with the habits of human thought, and domain events are an excellent tool for analyzing domain models. While using an event-driven programming model to consider some additional problems, such as online debugging, fault tolerance, resend, etc., but there’s little doubt that field events provides us with a better means of decoupling, can break up a large number of complex business logic in the process of different event subscribers, and again maintained a loose coupling relationship between each other. If the project allows, I highly recommend the domain event mode. If you are interested, try it!
This article is formatted using MDNICE
Welcome to follow my wechat account “And get more high-quality articles”