原文链接:  http://www.codeproject.com/Articles/555855/Introduction-to-CQRS

What is CQRS

CQRS means Command Query Responsibility Segregation. Many people think that CQRS is an entire architecture, but they are wrong. CQRS is just a small pattern. This pattern was first introduced by Greg Young and Udi Dahan. They took inspiration from a pattern called Command Query Separation which was defined by Bertrand Meyer in his book “Object Oriented Software Construction”. The main idea behind CQS is: “A method should either change state of an object, or return a result, but not both. In other words, asking the question should not change the answer. More formally, methods should return a value only if they are referentially transparent and hence possess no side effects.” (Wikipedia) Because of this we can divide a methods into two sets:

  • Commands - change the state of an object or entire system (sometimes called as modifiers or mutators).
  • Queries - return results and do not change the state of an object.

In a real situation it is pretty simple to tell which is which. The queries will declare return type, and commands will return void. This pattern is broadly applicable and it makes reasoning about objects easier. On the other hand, CQRS is applicable only on specific problems.

Many applications that use mainstream approaches consists of models which are common for read and write side. Having the same model for read and write side leads to a more complex model that could be very difficult to be maintained and optimized.

The real strength of these two patterns is that you can separate methods that change state from those that don’t. This separation could be very handy in situations when you are dealing with performance and tuning. You can optimize the read side of the system separately from the write side. The write side is known as the domain. The domain contains all the behavior. The read side is specialized for reporting needs.

Another benefit of this pattern is in the case of large applications. You can split developers into smaller teams working on different sides of the system (read or write) without knowledge of the other side. For example developers working on read side do not need to understand the domain model.

Query side

The queries will only contain the methods for getting data. From an architectural point of view these would be all methods that return DTOs that the client consumes to show on the screen. The DTOs are usually projections of domain objects. In some cases it could be a very painful process, especially when complex DTOs are requested.

Using CQRS you can avoid these projections. Instead it is possible to introduce a new way of projecting DTOs. You can bypass the domain model and get DTOs directly from the data storage using a read layer. When an application is requesting data, this could be done by a single call to the read layer which returns a single DTO containing all the needed data.

The read layer can be directly connected to the database (data model) and it is not a bad idea to use stored procedures for reading data. A direct connection to the data source makes queries very easy to by maintained and optimized. It makes sense to denormalize data. The reason for this is that data is normally queried many times more than the domain behavior is executed. This denormalization could increase the performance of the application.

Command side

Since the read side has been separated the domain is only focused on processing of commands. Now the domain objects no longer need to expose the internal state. Repositories have only a few query methods aside from GetById.

Commands are created by the client application and then sent to the domain layer. Commands are messages that instruct a specific entity to perform a certain action. Commands are named like DoSomething (for example, ChangeName, DeleteOrder ...). They instruct the target entity to do something that might result in different outcomes or fail. Commands are handled by command handlers.

Hide Shrink Copy Code

  1. public interface ICommand
  2. {
  3. Guid Id { get; }
  4. }
  5.  
  6. public class Command : ICommand
  7. {
  8. public Guid Id { get; private set; }
  9. public int Version { get; private set; }
  10. public Command(Guid id,int version)
  11. {
  12. Id = id;
  13. Version = version;
  14. }
  15. }
  16.  
  17. public class CreateItemCommand:Command
  18. {
  19. public string Title { get; internal set; }
  20. public string Description { get;internal set; }
  21. public DateTime From { get; internal set; }
  22. public DateTime To { get; internal set; }
  23.  
  24. public CreateItemCommand(Guid aggregateId, string title,
  25. string description,int version,DateTime from, DateTime to)
  26. : base(aggregateId,version)
  27. {
  28. Title = title;
  29. Description = description;
  30. From = from;
  31. To = to;
  32. }
  33. }

All commands will be sent to the Command Bus which will delegate each command to the command handler. This demonstrates that there is only one entry point into the domain. The responsibility of the command handlers is to execute the appropriate domain behavior on the domain. Command handlers should have a connection to the repository to provide the ability to load the needed entity (in this context called Aggregate Root) on which behavior will be executed.

Hide Shrink Copy Code

  1. public interface ICommandHandler<TCommand> where TCommand : Command
  2. {
  3. void Execute(TCommand command);
  4. }
  5.  
  6. public class CreateItemCommandHandler : ICommandHandler<CreateItemCommand>
  7. {
  8. private IRepository<DiaryItem> _repository;
  9.  
  10. public CreateItemCommandHandler(IRepository<DiaryItem> repository)
  11. {
  12. _repository = repository;
  13. }
  14.  
  15. public void Execute(CreateItemCommand command)
  16. {
  17. if (command == null)
  18. {
  19. throw new ArgumentNullException("command");
  20. }
  21. if (_repository == null)
  22. {
  23. throw new InvalidOperationException("Repository is not initialized.");
  24. }
  25. var aggregate = new DiaryItem(command.Id, command.Title, command.Description,
  26. command.From, command.To);
  27. aggregate.Version = -1;
  28. _repository.Save(aggregate, aggregate.Version);
  29. }
  30. }

The command handler performs the following tasks:

  • It receives the Command instance from the messaging infrastructure (Command Bus)

  • It validates that the Command is a valid Command
  • It locates the aggregate instance that is the target of the Command.
  • It invokes the appropriate method on the aggregate instance passing in any parameter from the command.
  • It persists the new state of the aggregate to storage.
Internal Events

The first question we should ask is what is the domain event. The domain event is something that has happened in the system in the past. The event is typically the result of a command. For example the client has requested a DTO and has made some changes which resulted in a command being published. The appropriate command handler has then loaded the correct Aggregate Root and executed the appropriate behavior. This behavior raises an event. This event is handled by specific subscribers. Aggregate publishes the event to an event bus which delivers the event to the appropriate event handlers. The event which is handled inside the aggregate root is called an internal event. The event handler should not be doing any logic instead of setting the state.

Domain Behavior

Hide Copy Code

  1. public void ChangeTitle(string title)
  2. {
  3. ApplyChange(new ItemRenamedEvent(Id, title));
  4. }

Domain Event

Hide Copy Code

  1. public class ItemCreatedEvent:Event
  2. {
  3. public string Title { get; internal set; }
  4. public DateTime From { get; internal set; }
  5. public DateTime To { get; internal set; }
  6. public string Description { get;internal set; }
  7.  
  8. public ItemCreatedEvent(Guid aggregateId, string title ,
  9. string description, DateTime from, DateTime to)
  10. {
  11. AggregateId = aggregateId;
  12. Title = title;
  13. From = from;
  14. To = to;
  15. Description = description;
  16. }
  17. }
  18.  
  19. public class Event:IEvent
  20. {
  21. public int Version;
  22. public Guid AggregateId { get; set; }
  23. public Guid Id { get; private set; }
  24. }

Internal Domain Event Handler

Hide Copy Code

  1. public void Handle(ItemRenamedEvent e)
  2. {
  3. Title = e.Title;
  4. }

Events are usually connected to another pattern called Event Sourcing (ES). ES is an approach to persisting the state of an aggregate by saving the stream of events in order to record changes in the state of the aggregate.

As I mentioned earlier, every state change of an Aggregate Root is triggered by an event and the internal event handler of the Aggregate Root has no other role than setting the correct state. To get the state of an Aggregate Root we have to replay all the events internally. Here I must mention that events are write only. You cannot alter or delete an existing event. If you find that some logic in your system is generating the wrong events, you must generate a new compensating event correcting the results of the previous bug events.

External Events

External events are usually used for bringing the reporting database in sync with the current state of the domain. This is done by publishing the internal event to outside the domain. When an event is published then the appropriate Event Handler handles the event. External events can be published to multiple event handlers. The Event handlers perform the following tasks:

  • It receives an Event instance from the messaging infrastructure (Event Bus).

  • It locates the process manager instance that is the target of the Event.
  • It invokes the appropriate method of the process manager instance passing in any parameters from the event.
  • It persists the new state of the process manager to storage.

But who can publish the events? Usually the domain repository is responsible for publishing external events.

Using the Code

I have created a very simple example that demonstrates how to implement the CQRS pattern. This simple example allows you to create diary items and modify them. The solution consists of three projects:

  • Diary.CQRS

  • Diary.CQRS.Configuration
  • Diary.CQRS.Web

The first one is the base project that contains all domain and messaging objects. The Configuration project is consumed by Web which is the UI for this example. Now let’s take a closer look at the main project.

Diary.CQRS

As I mentioned earlier, this project contains all the domain and messaging objects for this example. The only entry point for the CQRS example is the Command Bus into which commands are sent. This class has only one generic method Send(T command). This method is responsible for creating the appropriate command handler using CommandHandlerFactory. If no command handler is associated with a command, an exception is thrown. In other case, the Execute method is called in which a behavior is executed. The Behavior creates an internal event and this event is stored into an internal field called _changes. This field is declared in the AggregateRoot base class. Next, this event is handled by the internal event handler which changes the state of an Aggregate. After this behavior is processed, all the aggregate’s changes are stored into the repository. The repository checks whether there are some inconsistencies by comparison of the expected version of the aggregate and the version of the aggregate stored in the storage. If those versions are different, it means that the object has been modified by someone else and a ConcurrencyException is thrown. In other case the changes are stored in the Event Storage.

Repository

Hide Shrink Copy Code

  1. public class Repository<T> : IRepository<T> where T : AggregateRoot, new()
  2. {
  3. private readonly IEventStorage _storage;
  4. private static object _lockStorage = new object();
  5.  
  6. public Repository(IEventStorage storage)
  7. {
  8. _storage = storage;
  9. }
  10.  
  11. public void Save(AggregateRoot aggregate, int expectedVersion)
  12. {
  13. if (aggregate.GetUncommittedChanges().Any())
  14. {
  15. lock (_lockStorage)
  16. {
  17. var item = new T();
  18.  
  19. if (expectedVersion != -1)
  20. {
  21. item = GetById(aggregate.Id);
  22. if (item.Version != expectedVersion)
  23. {
  24. throw new ConcurrencyException(string.Format("Aggregate {0} has been previously modified",
  25. item.Id));
  26. }
  27. }
  28.  
  29. _storage.Save(aggregate);
  30. }
  31. }
  32. }
  33.  
  34. public T GetById(Guid id)
  35. {
  36. IEnumerable<Event> events;
  37. var memento = _storage.GetMemento<BaseMemento>(id);
  38. if (memento != null)
  39. {
  40. events = _storage.GetEvents(id).Where(e=>e.Version>=memento.Version);
  41. }
  42. else
  43. {
  44. events = _storage.GetEvents(id);
  45. }
  46. var obj = new T();
  47. if(memento!=null)
  48. ((IOriginator)obj).SetMemento(memento);
  49.  
  50. obj.LoadsFromHistory(events);
  51. return obj;
  52. }
  53. }

InMemoryEventStorage

In this simple example I have created an InMemoryEventStorage which stores all events into memory. This class implements the IEventStorage interface with four methods:

Hide Copy Code

  1. public IEnumerable<Event> GetEvents(Guid aggregateId)
  2. {
  3. var events = _events.Where(p => p.AggregateId == aggregateId).Select(p => p);
  4. if (events.Count() == 0)
  5. {
  6. throw new AggregateNotFoundException(string.Format(
  7. "Aggregate with Id: {0} was not found", aggregateId));
  8. }
  9. return events;
  10. }

This method returns all events for the aggregate and throws an error when there aren’t events for an aggregate which means that aggregate doesn’t exist.

Hide Shrink Copy Code

  1. public void Save(AggregateRoot aggregate)
  2. {
  3. var uncommittedChanges = aggregate.GetUncommittedChanges();
  4. var version = aggregate.Version;
  5.  
  6. foreach (var @event in uncommittedChanges)
  7. {
  8. version++;
  9. if (version > 2)
  10. {
  11. if (version % 3 == 0)
  12. {
  13. var originator = (IOriginator)aggregate;
  14. var memento = originator.GetMemento();
  15. memento.Version = version;
  16. SaveMemento(memento);
  17. }
  18. }
  19. @event.Version=version;
  20. _events.Add(@event);
  21. }
  22. foreach (var @event in uncommittedChanges)
  23. {
  24. var desEvent = Converter.ChangeTo(@event, @event.GetType());
  25. _eventBus.Publish(desEvent);
  26. }
  27. }

This method stores events into memory and creates every three events memento for the aggregate. This memento holds all state information for the aggregate and the version. Using mementos increases the performance of the application because it is not important to load all the events but just the last three of them.

When all events are stored, they are published by the Event Bus and consumed by the external Event Handlers.

Hide Copy Code

  1. public T GetMemento<T>(Guid aggregateId) where T : BaseMemento
  2. {
  3. var memento = _mementos.Where(m => m.Id == aggregateId).Select(m=>m).LastOrDefault();
  4. if (memento != null)
  5. return (T) memento;
  6. return null;
  7. }

Returns memento for aggregate.

Hide Copy Code

  1. public void SaveMemento(BaseMemento memento)
  2. {
  3. _mementos.Add(memento);
  4. }

Stores memento for aggregate.

Aggregate Root

The AggregateRoot class is the base class for all aggregates. This class implements the IEventProvider interface. It holds information about all uncommitted changes in the _changes list. This class also has an ApplyChange method which executes the appropriate internal event handler. The LoadFromHistory method loads and applies the internal events.

Hide Shrink Copy Code

  1. public abstract class AggregateRoot:IEventProvider
  2. {
  3. private readonly List<Event> _changes;
  4.  
  5. public Guid Id { get; internal set; }
  6. public int Version { get; internal set; }
  7. public int EventVersion { get; protected set; }
  8.  
  9. protected AggregateRoot()
  10. {
  11. _changes = new List<Event>();
  12. }
  13.  
  14. public IEnumerable<Event> GetUncommittedChanges()
  15. {
  16. return _changes;
  17. }
  18.  
  19. public void MarkChangesAsCommitted()
  20. {
  21. _changes.Clear();
  22. }
  23.  
  24. public void LoadsFromHistory(IEnumerable<Event> history)
  25. {
  26. foreach (var e in history) ApplyChange(e, false);
  27. Version = history.Last().Version;
  28. EventVersion = Version;
  29. }
  30.  
  31. protected void ApplyChange(Event @event)
  32. {
  33. ApplyChange(@event, true);
  34. }
  35.  
  36. private void ApplyChange(Event @event, bool isNew)
  37. {
  38. dynamic d = this;
  39.  
  40. d.Handle(Converter.ChangeTo(@event,@event.GetType()));
  41. if (isNew)
  42. {
  43. _changes.Add(@event);
  44. }
  45. }
  46. }

EventBus

Events describe changes in the system’s state. The primary purpose of the events is to update the read model. For this purpose I have created the EventBus class. The only behavior of the EventBus class is publishing events to subscribers. One event can be published to more than one subscriber. In this example there is no need for a manual subscription. The event handler factory returns a list of all EventHandlers that can process the current event.

Hide Copy Code

  1. public class EventBus:IEventBus
  2. {
  3. private IEventHandlerFactory _eventHandlerFactory;
  4.  
  5. public EventBus(IEventHandlerFactory eventHandlerFactory)
  6. {
  7. _eventHandlerFactory = eventHandlerFactory;
  8. }
  9.  
  10. public void Publish<T>(T @event) where T : Event
  11. {
  12. var handlers = _eventHandlerFactory.GetHandlers<T>();
  13. foreach (var eventHandler in handlers)
  14. {
  15. eventHandler.Handle(@event);
  16. }
  17. }
  18. }

Event Handlers

The primary purpose of event handlers is to take the events and update the read model. In the example below you can see the ItemCreatedEventHandler. It handles the ItemCreatedEvent. Using information from the event it creates a new object and stores it in the reporting database.

Hide Copy Code

  1. public class ItemCreatedEventHandler : IEventHandler<ItemCreatedEvent>
  2. {
  3. private readonly IReportDatabase _reportDatabase;
  4. public ItemCreatedEventHandler(IReportDatabase reportDatabase)
  5. {
  6. _reportDatabase = reportDatabase;
  7. }
  8. public void Handle(ItemCreatedEvent handle)
  9. {
  10. DiaryItemDto item = new DiaryItemDto()
  11. {
  12. Id = handle.AggregateId,
  13. Description = handle.Description,
  14. From = handle.From,
  15. Title = handle.Title,
  16. To=handle.To,
  17. Version = handle.Version
  18. };
  19.  
  20. _reportDatabase.Add(item);
  21. }
  22. }
Diary.CQRS.Web

This project serves as UI for the CQRS example. The Web UI project is a simple ASP.NET MVC 4 application with only one HomeController with six ActionResult methods:

  • ActionResult Index() - this method returns the Index view which is the main view of this application where you can see the list of all diary items.

  • ActionResult Delete(Guid id) - this method creates a new DeleteItemCommand and sends it to CommandBus. When a command is sent, the method will return Index view.
  • ActionResult Add() - returns Add view where you can input data for a new diary item.
  • ActionResult Add(DiaryItemDto item) - this method creates a new CreateItemCommand and sends it to the CommandBus. When a new item is created, the Index view is returned.
  • ActionResult Edit(Guid id) - returns the Edit view for a selected diary item.
  • ActionResult Edit(DiaryItemDto item) - this method creates a new ChangeItemCommand and sends it to the CommandBus. When an item is successfully updated, the Index screen is returned. In the case of ConcurrencyError, the edit view is returned and an exception is displayed on screen.

In the picture below you can see the main screen with a list of diary items.

When to use CQRS

In general, the CQRS pattern could be very valuable in situations when you have highly collaborative data and large, multi-user systems, complex, include ever-changing business rules, and delivers a significant competitive advantage of business. It can be very helpful when you need to track and log historical changes.

With CQRS you can achieve great read and write performance. The system intrinsically supports scaling out. By separating read and write operations, each can be optimized.

CQRS can by very helpful when you have difficult business logic. CQRS forces you to not mix domain logic and infrastructural operations.

With CQRS you can split development tasks between different teams with defined interfaces.

When not to use CQRS

If you are not developing a highly collaborative system where you don't have multiple writers to the same logical set of data you shouldn't use CQRS.

Introduction to CQRS的更多相关文章

  1. 浅谈命令查询职责分离(CQRS)模式

    在常用的三层架构中,通常都是通过数据访问层来修改或者查询数据,一般修改和查询使用的是相同的实体.在一些业务逻辑简单的系统中可能没有什么问题,但是随着系统逻辑变得复杂,用户增多,这种设计就会出现一些性能 ...

  2. 转:浅谈命令查询职责分离(CQRS)模式

    原文来自于:http://www.cnblogs.com/yangecnu/p/Introduction-CQRS.html 在常用的三层架构中,通常都是通过数据访问层来修改或者查询数据,一般修改和查 ...

  3. 查询职责分离(CQRS)模式

    查询职责分离(CQRS)模式 在常用的三层架构中,通常都是通过数据访问层来修改或者查询数据,一般修改和查询使用的是相同的实体.在一些业务逻辑简单的系统中可能没有什么问题,但是随着系统逻辑变得复杂,用户 ...

  4. 浅谈命令查询职责分离(CQRS)模式---转载

    在常用的三层架构中,通常都是通过数据访问层来修改或者查询数据,一般修改和查询使用的是相同的实体.在一些业务逻辑简单的系统中可能没有什么问题,但是随着系统逻辑变得复杂,用户增多,这种设计就会出现一些性能 ...

  5. CQRS+ES项目解析-Diary.CQRS

    在<当我们在讨论CQRS时,我们在讨论些神马>中,我们讨论了当使用CQRS的过程中,需要关心的一些问题.其中与CQRS关联最为紧密的模式莫过于Event Sourcing了,CQRS与ES ...

  6. 【转】浅谈命令查询职责分离(CQRS)模式

    原文链接:https://www.cnblogs.com/yangecnu/p/Introduction-CQRS.html 在常用的三层架构中,通常都是通过数据访问层来修改或者查询数据,一般修改和查 ...

  7. CQRS项目

    CQRS+ES项目解析-Diary.CQRS   在<当我们在讨论CQRS时,我们在讨论些神马>中,我们讨论了当使用CQRS的过程中,需要关心的一些问题.其中与CQRS关联最为紧密的模式莫 ...

  8. DDD CQRS架构和传统架构的优缺点比较

    明天就是大年三十了,今天在家有空,想集中整理一下CQRS架构的特点以及相比传统架构的优缺点分析.先提前祝大家猴年新春快乐.万事如意.身体健康! 最近几年,在DDD的领域,我们经常会看到CQRS架构的概 ...

  9. 谈一下关于CQRS架构如何实现高性能

    CQRS架构简介 前不久,看到博客园一位园友写了一篇文章,其中的观点是,要想高性能,需要尽量:避开网络开销(IO),避开海量数据,避开资源争夺.对于这3点,我觉得很有道理.所以也想谈一下,CQRS架构 ...

随机推荐

  1. Excel&&word&&PPT

    1. Excel 1.1 制作下拉框 选中单元格或列--> 菜单"数据" --> "数据验证"-->"设置" --> ...

  2. java中的输入输出方法

    输入 import java.util.Scanner; public class EnterTest { public static void main(String[] args) { //主方法 ...

  3. mysql 数据库8.0版本,jdbc驱动连接问题

    前言 8.0版本的mysql数据的连接 与 5.0的有所不同,下面直接贴出  8.0版本应该有的 jdbc驱动连接,还有 mysql 的jdbc jar包要8.0以上的 内容如下 : jdbc.dri ...

  4. nyoj 456——邮票分你一半——————【背包思想搜索】

    邮票分你一半 时间限制:1000 ms  |  内存限制:65535 KB 难度:3   描述      小珂最近收集了些邮票,他想把其中的一些给他的好朋友小明.每张邮票上都有分值,他们想把这些邮票分 ...

  5. win+R下的命令

    1.inetmgr 打开IIS 2.taskmgr 打开任务管理器 3.calc 打开计算器 4.msconfig 系统启动配置 5.mstsc  远程桌面 6.systeminfo 查看电脑信息 7 ...

  6. 数据类型之Nullable

    Nullable 此结构在 .NET Framework 2.0 版中是新增的.

  7. linux shell内置判断

    内置判断,成功的时候返回0,不成功返回非零 test  判断表达式 [ 判断表达式 ]       注意前后必须留空格哦 数值运算 -eq   等于 -ne   不等于 -gt     大于 -ge ...

  8. CPU调度

    概念 1.控制,协调进程对CPU的竞争,按一定的调度算法从就绪队列中选择一个进程把CPU的使用权交给被选中的进程, 如果没有就绪进程,系统会安排一个系统空闲进程或idle进程 cpu调度要解决的三个问 ...

  9. MockPlus的使用方法简介

    不废话直接上图,不明白的留言.

  10. python 日期排序

    转自:http://www.cnblogs.com/lkprof/p/3179850.html,感谢分享~ 问题1:如果日期中有千年以前的情况(没法用格式化函数),如('2010-11-23','19 ...