CQRS Mode Implementation

Source: Internet
Author: User
Tags net domain eventbus

[. NET domain-driven Design Combat series] Topic Ten: DDD Extension content: Comprehensive analysis of CQRS mode implementation I. INTRODUCTION

All of the topics presented above are based on classic domain-driven implementations, however, domain drivers can be implemented based on the CQRS model, in addition to the classic implementations. This topic will comprehensively analyze how to implement domain-driven design based on the CQRS Model (command query Responsibility segregation, mandate separation).

Second, what is CQRS?

Before introducing the concrete implementation, for the friend who did not know CQRS before, first question should be: What is CQRS ah? Do you introduce the concrete realization after the detailed introduction of CQRS? Since everyone will have such a problem, so the topic first comprehensive introduction of what is CQRS.

2.1 CQRS Development process

Before I introduce CQRS, I think it is necessary to first understand the CQS (that is, command query separation, order lookup separation) mode. We can understand that CQRS is an architectural pattern that appears in the practice of DDD based on CQS theory. The CQS model was first proposed by software guru Bertrand Meyer (Eiffel language's father, the object-oriented open-close principle OCP), and he argues that there are only two types of objects: commands and queries, and no third case. According to CQS's idea, any method can be split into two parts: command and query. For example, the following method:

        private int _number = 0;        public int Add (int factor)        {            _number + = factor;            return _number;        }

In the above method, a command is executed that adds a factor factor to the variable _number and executes a query that returns the value _number the query. According to CQS's thinking, this method can be split into command and query two methods:

private int _number = 0;private void AddCommand (int factor) {    _number + = factor;} private int QueryValue () {    return _number;}

The separation of commands and queries allows us to better grasp the details of the object and to better understand what operations will change the state of the system. This enables the system to be more scalable and to achieve better performance.

CQRS According to CQS thought, and combined with domain driven design ideas, by Grey Young in Cqrs, Task Based UIs, Event Sourcing agh! Presented in this article. CQRS will only need to define an object before splitting it into two objects, and the principle of separation is split by whether the method in the object executes a command or executes a query.

   2.2 CQRS Structure

  The previous introduction shows that the system structure implemented by the CQRS model can be divided into two parts: the command part and the query part. The system structure is as follows:

From the above system structure diagram, we can find that the domain driven design implemented by CQRS is very different from the classic ddd. The DDD structure implemented by CQRS is divided into two parts, the query part and the command part, and maintains two database instances, one is dedicated to query and the other responds to command operation. The state of the command change is then synchronized to the database instance used for querying through the EventHandler operation. From this description, we might associate a database-level master-Slave read-write separation. However, the data read and write separation is the mechanism to realize the separation of reads and writes at the database level, while CQRS is the implementation of the read/write separation mechanism at the business logic level. Both are implemented at two different levels for read-write separations.

Third, why need to introduce CQRS mode

We have introduced in detail the CQRS model, I believe that through the previous introduction, we must have some understanding of CQRS mode, but why to introduce CQRS mode?

In the traditional implementation, the implementation of the DB to increase, delete, change, check all operations will be placed in the corresponding warehousing, and these operations are common to a domain entity object. For some simple systems, there is nothing wrong with the traditional design approach, but in some large and complex systems, there are some problems with traditional implementations:

    • Using the same domain entity for data reading and writing may encounter resource contention. So often to deal with the problem of locks, when writing data, need to lock, read the data need to determine whether to allow dirty read. This increases the logic and complexity of the system and affects the throughput of the system.
    • Performance bottlenecks can occur in the event that large volumes of data are read and written at the same time.
    • Using the same domain entity to read and write the database may be too coarse. In most cases, such as editing, you might just need to update individual fields, but you need to put the whole object in. Also, when querying, the presentation layer may require only individual fields, but it needs to query and return the entire domain entity, and then transform the domain entity object from the corresponding Dto object.
    • Read and write operations are coupled, not conducive to the tracking and analysis of the problem, if the read-write operation is separated, if the problem is due to state change only need to analyze the write operation related logic can be, if it is about the data is incorrect, then only need to care about the relevant logic of the query operation.

For the above problems, the system with CQRS mode can be solved. Due to the analysis of queries and commands in the CQRS mode, it is clear that the two divisions are responsible for each other, and the separation of commands and queries in the business can improve the performance and scalability of the system. Since CQRS is so good, is it not all systems should be based on the CQRS model to achieve it? Obviously not, CQRS also has its use scene:

    1. The business logic of the system is more complicated than the case. Because the business logic is more complex, if the command and query operations are bound to the same business entity, this will lead to later changes in demand is difficult to expand.
    2. In situations where query performance and write performance are optimized separately in the system, especially when the read/write ratio is very high. For example, in many systems the number of requests for read operations is much larger than the write operation, at which point you might consider pulling out the write operation to expand it separately.
    3. The system will be changing over time in the future.

However, CQRS also has a scenario where it does not apply:

    • Business logic is relatively simple case, at this time the use of CQRS instead will make the system complex.
    • The system user access is relatively small case, and the demand is not how to change the case. For such a system, it is possible to implement the system quickly with the traditional implementation method, and there is no need to introduce CQRS to increase the complexity of the system.
Iv. Traceability of events

  In CQRS, the query aspect, directly through the method query the database, and then through the DTO to return the data, this aspect of the operation is relatively simple. The command side, by sending a specific command, and then distributed by Commandbus to the specific commandhandle for processing, commandhandle in the processing, does not directly save the state of the object to the external persistence structure, Instead of just getting a series of domain events from the domain object and saving the events to the event store, the event bus is then published to the next process, and then the event bus coordinates to give specific events Handle is processed, and the last event handler the state of the object to the corresponding query database.

  The above procedure is the order of calls in the CQRS system. It can be found that the system implemented by CQRS has two DB instances, one is the event store, which is used to save a series of domain events that occur in the domain object, which is simply the database that holds the domain events. The other is query database, which is the storage of specific domain object data, queries can directly query the database. Because, we record all events that occur in the domain object in the event store, so that we can query the DB instance to get all the states before the Realm object. The so-called event Sourcing refers to the origin of the object traced by the event, which allows the domain model to be restored to any previous point in time by logging events.

Event is used to record all states of a domain object, so that the system can be tracked and easily rolled back to a historical state. As described above, sensory event traceability is generally used for system maintenance. For example, we can design a synchronization service that queries historical data for a domain object from the event store database to print a historical report, such as a historical price report. But exactly how does the CQRS system use the event sourcing?

In the sequence of calls to the CQRS system described earlier, we mentioned that the event handler saved the state of the object to the corresponding query database, and there is a question as to how the state of the object is to be obtained. The object state is obtained by the event sourcing mechanism, because the user sends only the Command,command does not contain the state data of the object, so it is necessary to query the event through the event sourcing mechanism. Store to restore the state of an object, the restore is based on the corresponding ID, which is passed through the command. the invocation of the Event sourcing needs to be placed in Commandhandle, because Commandhandle needs to obtain the domain object before it can compare the domain object to the Command object to obtain a range of domain events generated in the domain object.

V. Snapshots

However, as domain events become more and more important over time, the process of restoring object state through the event sourcing mechanism can be time-consuming, because each time you need to start with the earliest events that occur. Is there a good way to solve this problem? The answer is yes, the introduction of snapshot (snapshots) implementation in event sourcing. The implementation principle is that--no N domain events are generated, then the object is taken as a snapshot. In this way, when the domain object is traceable, it is possible to get the most recent snapshot from the snapshot and then apply all the resulting domain events after the snapshot one after the other, without needing to reconstruct the object from the beginning of the first event, thus greatly speeding up the process of object rebuilding.

Six, CQRS pattern realization and analysis

As described in the previous section of CQRS, the following is a concrete example of the implementation of the CQRS system.

Implementation of the command section

  

    An application initialization operation that injects a dependent object through a dependency injection framework StructureMap public sealed class Servicelocator {private static readonly        Icommandbus _commandbus;        private static readonly IStorage _querystorage;        private static readonly bool isinitialized;                private static readonly Object LockThis = new Object (); Static Servicelocator () {if (!                    isinitialized) {Lock (LockThis) {//Dependency Injection                    Containerbootstrapper.bootstrapstructuremap ();                    _commandbus = containerbootstrapper.container.getinstance<icommandbus> ();                    _querystorage = containerbootstrapper.container.getinstance<istorage> ();                IsInitialized = true;        }}} public static Icommandbus Commandbus {get {return _commandbus;} } public static IStorage Querystorage {get {RetuRN _querystorage;        }}} class Containerbootstrapper {private static Container _container;                public static void Bootstrapstructuremap () {_container = new container (x = { X.for (typeof (Idomainrepository<>)). Singleton ().                Use (typeof (Domainrepository<>)); X.for<ieventstorage> (). Singleton ().                Use<inmemoryeventstorage> (); X.for<ieventbus> ().                Use<eventbus> (); X.for<icommandbus> ().                Use<commandbus> (); X.for<istorage> ().                Use<inmemorystorage> (); X.for<ieventhandlerfactory> ().                Use<structuremapeventhandlerfactory> (); X.for<icommandhandlerfactory> ().            Use<structuremapcommandhandlerfactory> ();        });        } public static Container Container {get {return _container;} }}public class Homecontroller:controller {        [HttpPost] public actionresult Add (diaryitemdto Item) {//Publish Createitemcommand to Commandbus Medium ServiceLocator.CommandBus.Send (New Createitemcommand (Guid.NewGuid (), item. Title, item. Description,-1, item. From, item.            To));        Return redirecttoaction ("Index"); }}//Commandbus implementation of public class Commandbus:icommandbus {private ReadOnly icommandhandlerfactory        _commandhandlerfactory; Public Commandbus (Icommandhandlerfactory commandhandlerfactory) {_commandhandlerfactory = CommandHandle        Rfactory;            The public void send<t> (T command) where T:command {//Gets the corresponding commandhandle to handle the command            var handlers = _commandhandlerfactory.gethandlers<t> (); foreach (var handler in handlers) {//processing command handler.            Execute (command); }}}//to Createitemcommand processing classes public class createitemcommandhandler:icommandhandler<createitemcommand> {private ReadOnly idomainrepository<diaryitem> _DO        Mainrepository;  Public Createitemcommandhandler (idomainrepository<diaryitem> domainrepository) {_domainrepository        = Domainrepository;            }//Specific processing logic public void Execute (Createitemcommand command) {if (command = = null)            {throw new ArgumentNullException ("command"); } if (_domainrepository = = null) {throw new InvalidOperationException ("Domainreposit            Ory is not initialized. "); var aggregate = new Diaryitem (command.id, command. Title, command. Description, command. From, command.            To) {Version =-1}; The corresponding domain entities are saved _domainrepository.save (aggregate, aggregate.        Version); }}//Idomainrepository implementation class public class Domainrepository<t>: Idomainrepository<t> where T:aggregateroot, new () {//does not save the domain entity directly, but first saves the domain event into Eventstore, and then Publis H event to Eventbus processing//Then eventbus the event to the corresponding event handler for processing, the event handler to save the domain object to querydatabase public void Save (Aggregateroo t aggregate, int expectedversion) {if (aggregate. Getuncommittedchanges (). Any ()) {_storage.            Save (aggregate); }}}//Event store implementation, which is stored in memory, usually saved to a specific database, such as SQL Server, MongoDB and other public class Inmemoryeventstorage:ieven            Tstorage {//domain event save public void Save (Aggregateroot aggregate) {//Get event not submitted by the corresponding domain entity var uncommittedchanges = aggregate.            Getuncommittedchanges (); var version = aggregate.                        Version;                foreach (Var @event in uncommittedchanges) {version++;        No 3 events Create a snapshot if (Version > 2) {if (version% 3 = = 0)            {var originator = (isnapshotorignator) aggregate; var snapshot = originator.                        CreateSnapshot (); Snapshot.                        Version = version;                    SaveSnapshot (snapshot); }} @event.                Version = version; Save the event to Eventstore in _events.            ADD (@event);                }//After the Save event is completed, the event is then published to Eventbus for further processing of foreach (Var @event in uncommittedchanges) { var desevent = Typeconverter.changeto (@event, @event.                GetType ());            _eventbus.publish (desevent); }}}//Eventbus implementation public class Eventbus:ieventbus {private ReadOnly ieventhandlerfactory _ev        Enthandlerfactory;        Public Eventbus (Ieventhandlerfactory eventhandlerfactory) {_eventhandlerfactory = eventhandlerfactory; } public void publish<t> (T @event) where t:domainevent {//get the corresponding eventhandle to handle the event var handlers = _eventhandlerfactory.gethandlers<t> (            );            foreach (Var eventHandler in handlers) {//Handle event Eventhandler.handle (@event); }}}//diaryitemcreatedevent event-handling class public class diaryiteamcreatedeventhandler:ieventhandler<        diaryitemcreatedevent> {private readonly istorage _storage;        Public Diaryiteamcreatedeventhandler (IStorage storage) {_storage = storage;                } public void Handle (Diaryitemcreatedevent @event) {var item = new Diaryitemdto () { Id = @event. SourceID, Description = @event. Description, from = @event. From, Title = @event. Title, to = @event. To, Version = @event.            Version}; Persists the domain object to querydatabase _storage.     ADD (item);   }    }     

The above code mainly demonstrates the command part of the implementation, from the code can be seen, first we need to use the Servicelocator class to inject the dependency injection object, and then the UI layer through the Commandbus to the corresponding command published to Commandbus for processing, The command bus then finds the corresponding CommandHandler to process the command, and then CommandHandler calls the storage class to save the event corresponding to the domain object, and then publishes the event to the event bus for processing after the Save event is successful. The domain object is then saved to querydatabase by the corresponding event handler. This completes the command part of the operation, it can be found that the implementation of the command part and CQRS system structure diagram of the processing process is the same. However, the Create log command does not involve an event traceability operation, because the creation of commands and the need to rebuild the domain objects are obtained by creating a log command, but the source of the event is involved in the Modify and delete commands because the domain object needs to be rebuilt according to the ID of the Command object. Specific implementation can refer to the source code.

Let's look at the implementation of the query section again.

Implementation code for the query section:

public class Homecontroller:controller    {        //Query part public        actionresult Index ()        {            // Get the QueryDatabase object directly to query all logs            var model = ServiceLocator.QueryStorage.GetItems ();            return View (model);        }    } public class Inmemorystorage:istorage    {        private static readonly list<diaryitemdto> Items = new List<d Iaryitemdto> ();        Public Diaryitemdto GetById (Guid id)        {            return items.firstordefault (a = a.ID = = id);        }        public void Add (Diaryitemdto item)        {            Items.Add (item);        }        public void Delete (Guid id)        {            Items.removeall (i = i.id = = id);        }        Public list<diaryitemdto> GetItems ()        {            return Items;        }    }

As can be seen from the above code, the query part of the code implementation is relatively simple, the UI layer directly through the QueryDatabase to query the domain object, and then rendered by the UI layer to display.

In this, a simple CQRS system is completed, however, in the project, the UI layer is not directly commandbus and querydatabase reference, but through the corresponding commandservice and queryservice to coordinate, The specific system structure is shown (just before Commandbus and query database are joined by a SOA service layer to coordinate, which facilitates the system expansion, through the SOA service to request routing, different requests are routed to different systems, This enables multiple systems to be consolidated):

As for the demo of the CQRS system, you can download it yourself on GitHub or MSDN, which will be given at the end of this topic.

Vii. Summary

Here, the presentation of the topic on CQRS is over, and the topic is the last of a series of field-driven designs. This series of topics mainly refer to the Byteartretail case of Daxnet, because daxnet in writing this case does not have a step-by-step introduction of its creation process, for some field-driven beginners, directly to learn this case will be a little difficult, resulting in lower learning interest, Thereby abandoning field-driven learning. In order to solve these problems, so, I byteartretail case analysis, and refer to the case step by stage to achieve their own field-driven case Onlinestore. I hope this series can help you open the door of domain-driven.

Now that the no-sql in the Internet industry is very popular, so that the interview is often asked about the non-relational database you used? So I do not want out, so in the last 2 months when learning some no-sql content, so, next, I will open a No-sql series, record their time to learn some of no-sql experience.

All source code downloads for this topic:

GitHub Address: Https://github.com/lizhi5753186/CQRSDemo

MSDN Address: Https://code.msdn.microsoft.com/CQRS-1f05ebe5

This article refers to links:

Http://www.codeproject.com/Articles/555855/Introduction-to-CQRS

Http://www.cnblogs.com/daxnet/archive/2010/08/02/1790299.html

Classification:. NET domain Driven Design series Tags: DDD, CQRS, Event Sourcing, Snapshot

CQRS Mode Implementation

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.