Cqrs practice (4): domain events

Source: Internet
Author: User
Tags to domain

The commands in cqrs are discussed in the previous articles, and the domain events in cqrs are discussed in this article ).

Concept

First, review the execution process of a UI operation in cqrs:

First, the user clicks a button in the UI, and then the UI Layer constructs a corresponding command object and puts it in commandbus for execution. During the Command Execution Process, classes and methods in the domain model are called, and domain events are generated at this time. They are called "Domain" events because they are generated in the domain model. This can be illustrated in the following figure (unitofworkcontext is ignored first ):

It can also be seen that the calling of the domain model is "wrapped" in the context of command execution. Therefore, all operations on the UI Layer are just creating commands, and then throwing the command to commandbus, instead of calling the classes and methods in the domain model directly.

Basic implementation

The implementation of domain events is somewhat similar to the implementation of command, but note that each command can only correspond to one commandexecutor, while a domain event can be bound to multiple event processors (eventhandler ). The following is an implementation of an initial version.Code:

View code

 ///   <Summary>  ///  Mark interface (Marker Interface), which must be implemented for all domain events. ///   </Summary>  Public   Interface  Ievent {}  ///   <Summary>  ///  Event processor interface, which must be implemented by all event processors.  ///   </Summary>  Public   Interface Ieventhandler < In Tevent> Where Tevent: ievent {  ///   <Summary>      ///  Process events.  ///   </Summary>      Void  Handle (tevent evnt );}  ///   <Summary>  ///   ///   </Summary>  Public   Static  Class  Eventbus {  Public   Static   Void Publish <tevent> (Tevent evnt)  Where  Tevent: ievent {  //  Obtain all the event processors bound to the input event type, traverse and execute  }} 

Suppose there is a demand: an online bookstore has a new book. We want to send a promotion email for the new book to the registered user after adding the book to the database.

Then we will have an addbookcommand during implementation. When this command is executed, bookaddedevent will be triggered, and an eventhandler in the system will be bound to this event, it sends the information of the new book to the registered user by email (other event handler may be available, such as the event handler used to update the website statistics ).

The addbookcommand will not be repeated. For details, refer to the "command implementation" article. The Code of bookaddedevent is as follows, which implements the ievent interface:

View code

 
Public ClassBookaddedevent: ievent {Public StringBookisbn {Get;Set;}Public DecimalPrice {Get;Set;}}

The corresponding bookaddedeventhandler code is as follows:

View code

Public ClassBookaddedeventhandler: ieventhandler <bookaddedevent>{Public VoidHandle (bookaddedevent evnt ){VaRMSG ="New book! ISBN:"+ Evnt. bookisbn +", Price:"+Evnt. price;//Send emails to users}}

Different from commands, commands express an operation to be executed, and events express an event that has occurred. Therefore, the naming of event classes is in the past. Like commands, the class names of domain events have clear semantics. Therefore, the principle of "never reuse at will" in "command implementation" applies to domain events.

Improvement implementation

The above implementation ignores a very important issue: the bookaddedeventhandler sent an email to the user, and domain events were generated during the call of the domain model, this means that the database transaction has not been committed when sending the mail. What if the database transaction commit fails? The new book is not added, but the email is sent out, which is unacceptable. Therefore, for event handler such as sending emails, we must ensure that they are executed only after the transaction is committed successfully.

However, we cannot put all event handler into execution after the event is submitted successfully. For example, after adding a new book, we need to add one to the total number of books in the website statistics (assuming that the statistics are stored in a table, this statistical information can be considered as readmodel in cqrs ), at this time, the increase in the total number of books should be in the same database transaction as the addition of books. For such eventhandler, it should be executed immediately when domain events are triggered.

Therefore, we can make some changes to the above domain events: eventhandler is divided into two types, one is normal direct execution of eventhandler, one is postcommiteventhandler that is executed only after the database transaction is committed successfully. We will introduce unitofworkcontext. When the command starts to be executed, we will create a new unitofworkcontext object which will always exist during the command execution. During command execution, once a domain event is triggered, we immediately execute all the common eventhandler bound to the event, and then add the event to the current unitofworkcontext, after unitofwork is submitted successfully, traverse all the domain events added to the current unitofworkcontext and execute the corresponding postcommiteventhandler one by one. After the command is executed, close the current unitofworkcontext. The related code is as follows (for the complete code, see the taro project mentioned at the end of this article ):

First, we need to divide the eventhandler interface into two types: ieventhandler <tevent> and ipostcommiteventhandler <tevent>:

View code

 Public   Interface Ieventhandler < In Tevent> Where  Tevent: ievent {  ///   <Summary>      ///  Process events.  ///   </Summary>      Void  Handle (tevent evnt );}  Public  Interface Ipostcommiteventhandler < In Tevent> Where  Tevent: ievent {  Void  Handle (tevent evnt );} 

Then there is unitofwork (the unimportant code has been removed ):

View code

 Public   Abstract   Class  Abstractunitofwork: iunitofwork {  //  Ieventhandlerfinder is used to obtain all eventhandler bound to the event.     Private  Ieventhandlerfinder _ eventhandlerfinder;  Public Icollection <ievent> uncommittedevents { Get ; Private   Set  ;}  Public   Void  Commit () {commitchanges ();  //  After the database transaction is committed, execute all ipostcommiteventhandler  Invokepostcommithandlers ();} Protected   Abstract   Void  Commitchanges ();  Protected   Virtual   Void  Invokepostcommithandlers (){  //  Traverse domain events and execute the corresponding ipostcommiteventhandler          Foreach ( VaR Evnt In  Uncommittedevents ){ Foreach ( VaR Handler In  _ Eventhandlerfinder. findpostcommithandlers (evnt) {eventhandlerinvoker. Invoke (handler, evnt) ;}} uncommittedevents. Clear ();}} 

Then there is unitofworkcontext, which is very simple and implemented using threadstatic:

View code

 Public   Static   Class  Unitofworkcontext {[threadstatic]  Private   Static Iunitofwork _ current;  Public   Static  Iunitofwork current {  Get  {  Return  _ Current ;}}  Public   Static   Void  Open (iunitofwork unitofwork) {_ current = Unitofwork ;}  Public  Static   Void  Close () {_ current = Null  ;}} 

We also need a abstractcommandexecutor abstract base class to control the enabling and disabling of unitofworkcontext (all commandexecutor must inherit abstractcommandexecutor ):

View code

 Public   Abstract   Class Abstractcommandexecutor <tcommand>: icommandexecutor <tcommand> Where  Tcommand: icommand {  Protected Func <iunitofwork> getunitofwork { Get ; Private   Set  ;}  Protected Abstractcommandexecutor (func <iunitofwork> Getunitofwork) {require. notnull (getunitofwork,  "  Getunitofwork  "  ); Getunitofwork = Getunitofwork ;}  Public   Void Execute (tcommand cmd ){  Using ( VaR Uow = Getunitofwork () {unitofworkcontext. Open (uow );  Try  {Execute (uow, CMD );}  Finally  {Unitofworkcontext. Close ();}}}  Protected   Abstract   Void Execute (iunitofwork unitofwork, tcommand cmd );} 

To enable the domain model to easily trigger domain events, we add a static domainevent class. In its apply method, we first execute the ordinary eventhandler and then add the event to the current unitofworkcontext:

View code

 Public   Static   Class  Domainevent {  Public   Static   Void Apply <tevent> (Tevent evnt)  Where  Tevent: ievent { VaR Handlerfinder = Eventhandlerfinders. Current;  //  Locate all common eventhandler bound to the event and execute          Foreach ( VaR Handler In  Handlerfinder. findprecommithandlers (evnt) {eventhandlerinvoker. Invoke (handler, evnt );}  VaR Unitofwork = Unitofworkcontext. Current;  If (Unitofwork = Null )  Throw   New Invalidoperationexception ( "  Current unit of work context is null. Domain events can only be applied inside a unit of work context.  "  );  //  Add domain events to the current unitofworkcontext  Unitofwork. uncommittedevents. Add (evnt );}} 

Finally, we can call the domain model as follows:

//Library repository, pseudocodePublic ClassBookwarehouse {PublicIlist <book> books {Get;Private Set;}Public VoidAddbook (Book) {books. Add (book );//Triggering domain eventsDomainevent. Apply (NewBookaddedevent (book ));}}
Summary

This article discusses the basic concepts and implementation of domain events, and makes further improvements to solve the problem that some eventhandler can execute after the database transaction is committed successfully.

The introduction of domain events is extremely important. It makes the domain model more pure and decouples different logics. If there is no domain event, the code for receiving the new book into the database and the Code for sending the promotion email and changing the website statistics must be coupled. Domain events are not private products of cqrs. They can be separated from cqrs and exist independently as lightweight components.

So far, the main components of cqrs have been discussed. Other advanced topics, such as event sourcing and asynchronous event distribution, will not be discussed in this series. In the next article, we will use a mini cqrs framework (Taro) and a running online bookstore (bookstore) Sample project to demonstrate how to apply cqrs in actual projects.

Welcome to the discussion.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.