In the last code check-in,Byteart retailDomain events can be defined and processed. In this article, I will introduce in detail the specific implementation of the domain event mechanism in byteart retail case.
During Domain Modeling, we knew the necessity to ensure the purity of Domain Models. In short, each object in the domain model should be a poco (pojo) object, rather than adding any content related to the technical architecture to it. Udi Dahan once said: "The main assertion being that you do * not * need to inject anything into your domain entities. Not services. Not repositories. Nothing .". Therefore, a friend previously suggested whether to access the warehouse in the domain model? The answer is no. What about domain service? Of course not. By the way, the domain service in the current version of byteart retail accesses the warehouse. This is not a reasonable practice. I will make improvements in the next version. What should I do if I need to access these technical aspects under some business needs? For example, when the system administrator completes the delivery of the sales order, he wants to send an email to the customer. Domain events are required.
Domain events are a classification of many events in the application system. Enterprise Application events can be divided into three types: system events, application events, and domain events. The domain event trigger point is in the domain model. By using domain events, we can implement asynchronous updates of the state of Domain Model objects, delegate calls to external system interfaces, and system integration through the event dispatching mechanism. In the process of actual business analysis, if there is "when a occurs, we need to do B" in the general language ." This description indicates that a can be defined as a domain event. Domain events are generally named in the form of "event generation object name + completed actions", such as orderdispatchedevent) orderconfirmedevent. In the source code of the current byteart retail case, these two domain events are introduced. In fact, for this case, there are still many areas where domain events can be used, such as when the customer address is changed, the event processor can be used to update the customer shipping addresses of all undelivered orders before the event occurs. Of course, for the sake of simplicity, the case only demonstrates the above two events.
In addition, domain events are self-descriptive. It not only expresses what happened to the system, but also describes the motive of the event. For example, addresschangedevent can derive two Derived classes: contactmovedevent and addresscorrectedevent. Although these two events will lead to address information changes, their motivations are different: the former shows that the address change is because the contact's address has changed, while the latter shows that the address change is because the address information was originally incorrect and is now corrected.
Now, we will gradually discuss how domain events are implemented in byteart retail cases.
Define a domain event
Generally, we define an interface (idomainevent Interface) for domain events. All types that implement this interface are considered to be the types of domain events. To provide complete information to event management agencies such as the event processor, we can set some attributes in this interface, such as the time stamp of the event, the event source, and the event id value, of course, these contents are based on specific project requirements. In the byteart retail case, an abstract class (domainevent class) is defined, which implements the idomainevent interface and provides a constructor with parameters, it accepts a domain entity representing the event source as a parameter. Therefore, in the entire byteart retail convention, all domain event types inherit from the domainevent type, to force each type to provide a constructor with the same parameter type. The advantage of doing so is that every time a developer initializes a domain event, the source of the event must be set up, and a contract must be reached for development, effectively reducing the generation of errors.
For example, the orderdispatchedevent mentioned above is defined as follows:
/// <Summary> /// indicates the domain events generated when a sales order is delivered. /// </Summary> public class orderdispatchedevent: domainevent {# region ctor /// <summary> /// initialize a new <C> orderdispatchedevent </C> instance. /// </Summary> /// <Param name = "Source"> event source object that generates a domain event. </Param> Public orderdispatchedevent (ientity source): Base (source) {}# endregion # region public properties /// <summary> // gets or sets the date of order delivery. /// </Summary> Public datetime dispatcheddate {Get; Set ;}# endregion}
In this event definition, the constructor accepts an ientity parameter to indicate the object that generates the current event. In addition, it also contains the date information of the Order shipment.
Distribution and handling of domain events
The mechanism for processing domain events is called "event handler", and the dispatching of domain events is implemented through "event aggregator. Next, we will discuss the implementation process of these two parts.
Event Handler)
The task of the event processor is to process captured events. Its responsibilities are relatively simple: you only need to process the incoming information. Therefore, we can define it as a generic interface in implementation. For example, in byteart retail, it is defined as the idomaineventhandler <tdomainevent> interface, the tdomainevent type parameter specifies the type of domain events that the event processor can process. Generally, this interface provides only one handle method. This method accepts an object of the tdomainevent type (that is, a domain event instance) as a parameter. All types that implement this interface are considered as event processors capable of processing events of specific types of domains. Similar to the design of domain events, in byteart retail, a generic abstract class named domaineventhandler <tdomainevent> is also provided, which directly implements the idomaineventhandler <tdomainevent> interface, at the same time, an asynchronous event processing method is implemented: handleasync. Similarly, in byteart retail, all domain event processors should inherit from the domaineventhandler <tdomainevent> abstract class and implement the abstract method: handle method. BecauseTemplate Method ModeDevelopers do not need to consider the implementation of asynchronous event processing (that is, the handleasync method creates a task object for asynchronous task processing to execute the operations defined by the handle method ).
In addition, to simplify the programming model, byteart retail also supports delegate-based event processors. This design is not necessary, but in byteart retail, in order to simplify the event subscription operation, we still introduce such a delegate-based event processor. In some cases, the event processing logic is relatively simple. For example, to update the state of a domain object only when an event is captured, for such application scenarios, developers do not need to define a separate event Processor type for each relatively simple event processing logic. Instead, they only need to subscribe to and process events using the anonymous delegate method, this method is not only simple but also easy for standalone testing. We will discuss how event processors subscribe to domain events in the next section "event aggregators. Let's take a look at how the Delegate-based event processor is implemented in byteart retail.
In byteart retail, there is a special domain event processor. Like other domain event processors, it also inherits from the domaineventhandler <tdomainevent> generic abstract class, but its particularity lies in that, the constructor accepts a delegate of the Action <tdomainevent> type as a parameter.Decorator ModeAction <tdomainevent> delegate "decoration" to an object of the domaineventhandler <tdomainevent> type:
/// <Summary> /// indicates the domain event processor delegated by the proxy for the given domain event processing. /// </Summary> /// <typeparam name = "tevent"> </typeparam> internal sealed class metadata <tevent>: domaineventhandler <tevent> where tevent: class, idomainevent {# region private fields private readonly action <tevent> eventhandlerdelegate; # endregion # region ctor // <summary> // initialize a new <C> actiondelegateddomaineventhandler {tevent} </C> instance. /// </Summary> /// <Param name = "eventhandlerdelegate"> used for the event processing delegate delegated by the event processor in the current domain. </Param> Public actiondelegateddomaineventhandler (Action <tevent> eventhandlerdelegate) {This. eventhandlerdelegate = eventhandlerdelegate;} # endregion // temporarily ignore other functions and attributes}
In this class, the implementation of the handle method is very simple:
/// <Summary> /// process the specified event. /// </Summary> /// <Param name = "evnt"> events to be processed. </Param> Public override void handle (tevent evnt) {This. eventhandlerdelegate (evnt );}
The advantage of this approach is that the delegated event processor can be treated as a common event Processor type, so as to unify the Interface Definition of event subscription and event distribution.
It should be noted that, for actiondelegateddomaineventhandler, the equality between instances is not determined by the instance itself, but by the delegate of the instance, this has an important impact on the event processor's subscription to the event, and the event aggregator's dispatch to the event. Based on this analysis, we need to overload the equals method and use the delegate. Equals Method to Determine the equality of the two delegates. In byteart retail, the idomaineventhandler <tdomainevent> interface also implements the iequatable interface. Therefore, you only need to reload the equals method defined in the iequatable interface:
/// <Summary> /// obtain a <see CREF = "Boolean"/> value, which indicates whether the current object is equal to another object of the given type. /// </Summary> /// <Param name = "other"> another object of the same type as the current object to be compared. </Param> /// <returns> if the two are equal, true is returned. Otherwise, false is returned. </Returns> Public override bool equals (idomaineventhandler <tevent> Other) {If (referenceequals (this, other) return true; If (object) Other = (object) null) return false; then <tevent> otherdelegate = Other as actiondelegateddomaineventhandler <tevent>; If (object) otherdelegate = (object) null) return false; // use delegate. the equals method determines whether the two delegates are the same proxy method. Return delegate. Equals (this. eventhandlerdelegate, otherdelegate. eventhandlerdelegate );}
Now we have defined the event processor interface and related classes, and also implemented several simple event processors as needed (for specific code, refer to byteart retail in the byteartretail case. domain. events. classes in the handlers namespace ). Next, we need to enable the domain model to trigger domain events where the business needs them, and enable these event processors to process the events they have obtained. In the byteart retail case, this part is implemented using the "event aggregator.
Event aggregator)
Event aggregatorIt is an enterprise application architecture model. It is mainly used to aggregate the event processor in the domain model, so that when an event is triggered, the aggregated event processor can process the event. In byteart retail, the event aggregation structure is as follows:
In this design, event aggregators provide three interfaces: publish, subscribe, and unsubscribe. The subscribe interface is mainly used to register the processor of a specified type of event with the event aggregator. In this case, the event processor is listening for (subscribing) an event; the unsubscribe function is the opposite: it disconnects an event processor from listening to a specified type of events, that is, when an event is triggered, the event processor that no longer listens to the event will not execute the processing task; as for the publish interface, it is very simple: the domain model uses the publish interface to directly distribute events to the event aggregator, when an event occurs, the event aggregator forwards the processing permission to the processor listening for the event. The introduction of event aggregation enables events to be distributed at a time and processed in multiple places. It provides scalability for the application's domain event processing architecture and simplifies the event subscription process.
In byteart retail, the event aggregator is a static class and is not designed as an instance class because we cannot inject it into the domain model in any form, more importantly, it is impossible for a domain object to provide a constructor with the eventaggregator parameter. This is related to maintaining the purity of the domain model. For the specific implementation code of event aggregator, see the domaineventaggregator class in the byteartretail. domain. Events namespace. Next, we will summarize the process of generating, subscribing, distributing, and processing domain events.
Subscription, distribution, and handling of domain events
First, before the domain model participates in the business logic, the application architecture needs to subscribe to the domain events to be processed. In the classic hierarchy architecture for DDD, the role of the application layer is to coordinate task execution of various components (such as transactions, warehousing, and domain models, therefore, domain event subscription should also be performed when the application layer service is initialized. Specifically, in the byteart retail case, it is performed in the constructor of the Application Service.
It is of the orderserviceimpl type (this type is in byteartretail. application. implementation namespace) as an example. In the constructor, we extend a parameter: an array of the idomaineventhandler <orderdispatchedevent> type, and then use the domaineventaggregator class in the constructor, subscribe to the input event processor:
public OrderServiceImpl(IRepositoryContext context, IShoppingCartRepository shoppingCartRepository, IShoppingCartItemRepository shoppingCartItemRepository, IProductRepository productRepository, IUserRepository customerRepository, ISalesOrderRepository salesOrderRepository, IDomainService domainService, IDomainEventHandler<OrderDispatchedEvent>[] orderDispatchedDomainEventHandlers) :base(context){ this.shoppingCartRepository = shoppingCartRepository; this.shoppingCartItemRepository = shoppingCartItemRepository; this.productRepository = productRepository; this.userRepository = customerRepository; this.salesOrderRepository = salesOrderRepository; this.domainService = domainService; this.orderDispatchedDomainEventHandlers.AddRange(orderDispatchedDomainEventHandlers); foreach (var handler in this.orderDispatchedDomainEventHandlers) DomainEventAggregator.Subscribe<OrderDispatchedEvent>(handler); DomainEventAggregator.Subscribe<OrderConfirmedEvent>(orderConfirmedEventHandlerAction); DomainEventAggregator.Subscribe<OrderConfirmedEvent>(orderConfirmedEventHandlerAction2);}
The last two lines in the constructor subscribe to the event processing delegate related to orderconfirmedevent to demonstrate the implementation method of the Delegate-based event processor. The two delegates are defined in the form of read-only fields in the orderserviceimpl type:
private readonly Action<OrderConfirmedEvent> orderConfirmedEventHandlerAction = e => { SalesOrder salesOrder = e.Source as SalesOrder; salesOrder.DateDelivered = e.ConfirmedDate; salesOrder.Status = SalesOrderStatus.Delivered; };private readonly Action<OrderConfirmedEvent> orderConfirmedEventHandlerAction2 = _ => { };
The orderconfirmedeventhandleraction2 definition is nothing more than a demo (demonstrate the event processor to be discussed next), so I did not fill in any processing logic in this anonymous method. As for the idomaineventhandler <orderdispatchedevent> array parameter of the constructor, It is injected through unity. Modify the Web. config file of the server:
Next, after completing the operation at the application layer, the event processor needs to unsubscribe to the event (I .e. unsubscribe). To implement this function, I modified the iapplicationservicecontract interface definition, and let the applicationservice class inherit from the disposableobject class. Then, on the WCF Service, set the instancecontextmode to persession, that is, each time the WCF client establishes a connection with the server, it creates a service instance, when the client closes and revokes the connection, the service instance is destroyed. Therefore, after completing these structural adjustments, the dispose method of applicationservice is called every time a WCF session is completed. Then the specific implementation of each application layer service (orderserviceimpl, productserviceimpl, userserviceimpl, postbackserviceimpl) only needs to overload the dispose method according to their own needs, you can remove the event processor's subscription to the event in the dispose method:
protected override void Dispose(bool disposing){ if (disposing) { foreach (var handler in this.orderDispatchedDomainEventHandlers) DomainEventAggregator.Unsubscribe<OrderDispatchedEvent>(handler); DomainEventAggregator.Unsubscribe<OrderConfirmedEvent>(orderConfirmedEventHandlerAction); DomainEventAggregator.Unsubscribe<OrderConfirmedEvent>(orderConfirmedEventHandlerAction2); }}
Finally, the triggering of domain events is very simple: directly call domaineventaggregator. Publish. The entire process can be roughly described in the following sequence diagram:
Now, we have a general understanding of the design and implementation of the domain Events section in the byteart retail case. The review includes the definition of domain events, event processor, and event aggregation, and the relationship between these components. If readers can carefully read the source code of this case, I believe they can learn more in-depth details. However, we still need to extend the scope of the discussion to a higher level: Application Event ). Although it is beyond the scope of domain events, I still want to introduce it in this article, because this concept can easily cause developers to confuse the event category.
Is there any problem?
At the beginning of this article, we proposed a simple application scenario: "When the system administrator completes the delivery of sales orders, he wants to send an email to the customer ", this is the most common requirement. Although "delivery of finished sales orders" is defined as a domain event (in fact, it is also a domain event), but the logic for processing email sending, it is not a task of domain event processor. It is not difficult to know through analysis that the processing of domain events by the domain event processor lies in the process before the entire transaction is committed. The domain event processor can obtain or set the state of the domain object in a more complex way. However, the domain event processor is not a good choice for transaction-related event processing. Imagine that if the domain event processor sends an email, but the subsequent transaction commit fails, the order status received by the customer is inconsistent with the actual status.
The correct method should be to record domain events when they are triggered, and convert recorded domain events into application events when the transaction is committed, and distributed to the event bus. The distribution process can be synchronous or asynchronous. The subsequent email sending logic is executed by the event processor listening for the event bus. This involves a distributed transaction processing problem. For the function of "sending emails", I think the requirements for Distributed Transaction Processing should not be so obvious: After the database transaction is committed successfully, let the infrastructure layer component send an email directly. If an email fails to be sent, you do not need to roll back the database transaction. The customer complained that the email was not received. The system administrator can use Event Logs to troubleshoot the email sending function. However, for some application events, such as after a successful reservation, the system will send the successful reservation event to the payment system. After the payment system fails to make the payment for multiple attempts, the room unreservation logic needs to be completed to prevent the room from being occupied without limit. In these scenarios, distributed transaction processing is necessary (of course, you can also say that the payment system has no limit to retry, or find sales rep and perform 7x24 tracking to solve the transaction problem, but we will not consider these solutions for the moment ).
Byteart retail considers the possibility of these problems and roughly makes the following changes in the Event System and warehouse:
- An ibus interface is introduced. The application event processor can listen on this interface to receive application events to be processed. The application layer can also use this interface to dispatch application events.
- An event bus for event dispatcher is implemented. By using event dispatcher, the byteart retail event bus supports three different event Dispatching Methods: sequential, parallel, and parallelnowait (see the comments in the Code)
- Changed the implementation of the aggregateroot abstract class and introduced the part of the storage domain event.
- The implementation of the repositorycontext abstract class is changed. In the commit method, not only the commit transaction of the warehouse itself is executed (the new docommit method ), in addition, domain events stored in the aggregation root are distributed to the event bus. The event bus defines whether it supports distributed transaction processing. The repositorycontext determines whether to enable Distributed Transaction Coordinator based on this setting (however, In the Message Queue solution, only MSMQ can support Ms DTC)
The detailed implementation part will not be described here. Please read the source code of this case, especially byteartretail. events and byteartretail. events. type code in the handlers namespace.
Execution result
At the end of this article, let's take a look at the implementation results of domain events. Taking the system administrator shipping as an example, the rational system will generate an orderdispatchedevent domain event. The domain model updates the order delivery date and status through the domain event processor. At the same time, domain events will be saved to the aggregation root. When an order update is submitted, the saved domain events are distributed to the event bus, and then the email sending processor will capture the event and send it to the customer.
First, start the byteart retail WCF Service and ASP. net mvc application, log on with the daxnet/daxnet account, and ensure that the email address of the account is correctly set in the account settings. Then, use this account to purchase any item in the system. After the order is placed, log out of the system and use the admin/admin account to log on, on the "manage"-> "sales order management" Page, find the order you just received and click "ship" to process the shipment:
After the shipping operation is completed, you can see that the Order's shipping date and current status change accordingly:
Check the mailbox of the daxnet account and find that we have received a "order delivered" Notification Email (of course, this notification email is relatively simple for demonstration purposes, developers can enrich the mail content according to their actual situation to meet the actual project requirements ):
Summary
This article provides a feasible design scheme for the byteart retail case. This article introduces various event-related components and their implementation methods, and discusses the practical problems encountered in the implementation process (such as distributed transaction processing ). At the end of the article, we return to the byteart retail case to demonstrate the Interface Effects of domain events. In fact, events are a very complex but important component of the system architecture. Only the dispatching and processing of events can involve many technical points, such: event Processing workflows, asynchronous dispatching, parallel processing, message routing, and so on. As a case program, byteart retail cannot cover all aspects of these technologies. This requires developers to brainstorm and analyze based on the actual situation of their projects, summarize a set of more reasonable (or more suitable for your project) design solutions.
About byteart retail source code
As I mentioned above, my. Net-based domain-driven design framework (apworks) and byteart retail case source code have been moved to GitHub. The following is the source code Homepage Address of the byteart retail case:
Https://github.com/daxnet/ByteartRetail
A git user can use the following command to clone the code to the local machine:
Git clone https://github.com/daxnet/ByteartRetail.git
Finally, I would like to thank you for your support for my blog, apworks, and byteart retail case projects.