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.
CQRS意味着命令与查询的责任分离。 很多人认为CQRS是整体架构,但他们错了。CQRS只是一个设计模式。 这种模式是首先由Greg Young和Udi Dahan提出。他们的灵感来源于Bertrand Meyer的书"Object Oriented Software Construction"中所定义命令查询分离的模式。在CQS的主要理念就是是:"方法应该可以更改对象的状态或者返回结果,但不能同时兼具。换句话说,问问题的时候就不应该变更答案。 更正式点,方法仅仅在它们是显示引用并且没有其它作用时返回一个值。"(维基百科),因为这种方法我们可以划分为两个设置:
- 命令 -改变对象或整个系统的状态(有时称为修饰符或赋值函数)。
- 查询 -返回结果,并不会改变对象的状态。
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.
在实际环境中它是非常简单的分辨出哪个是哪个。查询将声明返回类型,而命令将返回void。这种模式是广泛适用的,它使对象的推理更容易。另一方面,CQRS只适用于特定的问题。
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.
许多应用程序使用主流方法包括对models通常的读和写操作。对相同的model的读取和写入会导致一个更复杂的模型,可能很难被维护和优化.
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.
这两种模式的真正作用是你不能用分开的方法改变他们状态。当你正在处理的性能和调优的情况下可能是非常方便,你可以分开写入端去优化系统的读取端。写入端被作为一个领域(domain)。 这个领域包含的所有行为。读取端是专门用于报告需求.
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.
Query 端
查询将只包含获取数据的方法。从架构的角度来看,这些将是在屏幕上显示客户消费返回DTOs(数据传递对象)的所有方法。DTOs直接来自使用一个read层的数据存储。在某些情况下,它可能是一个非常痛苦的过程,尤其是当要求复杂的DTO。
使用CQRS你能避免这些预测。相反,它是可以引入一个新的DTO的投影方式。您可以绕过域模型和DTO的直接使用读出层从数据存储。当一个应用程序请求数据,这可能是由一个单向的调用read层返回单个包含所有需要的数据DTO。
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.
read层可以直接连接到数据库(数据模型),并使用存储过程读取数据并不算一个烂想法。直接连接到数据源的查询非常容易维护和优化,这样做的原因是,数据通常是查询执行多次超过了领域的行为,这种非规范化可以提高应用程序的性能,非规范化的数据是有道理的。
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
.
命令端
由于读取端已经被分开,doamin(也就是写入端)只关注处理命令。现在的领域对象对象不再需要公开的内部状态。储存库除了除了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.
命令通过客户端应用程序创建然后发送到domain层.命令就是指示特定的实体来执行特定的动作的消息。命令被命名为像DoSomething (例如,ChangeName,DeleteOrder ...)。他们指示目标实体去做一些可能会导致不同的结果或者失败的事情。命令集 command handlers操作。
public interface ICommand{ Guid Id { get; }}public class Command : ICommand{ public Guid Id { get; private set; } public int Version { get; private set; } public Command(Guid id,int version) { Id = id; Version = version; }} public class CreateItemCommand:Command{ public string Title { get; internal set; } public string Description { get;internal set; } public DateTime From { get; internal set; } public DateTime To { get; internal set; } public CreateItemCommand(Guid aggregateId, string title, string description,int version,DateTime from, DateTime to) : base(aggregateId,version) { Title = title; Description = description; From = from; To = to; }}
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.
所有命令将被发送到命令总线,它委托Command handler处理每一个Command 。这表明,领域(domain)只有一个入口点。Command handlers应该有一个连接到存储库提供加载所需的实体的能力(在这种上下文称为聚合根)。
public interface ICommandHandlerwhere TCommand : Command{ void Execute(TCommand command);}public class CreateItemCommandHandler : ICommandHandler { private IRepository _repository; public CreateItemCommandHandler(IRepository repository) { _repository = repository; } public void Execute(CreateItemCommand command) { if (command == null) { throw new ArgumentNullException("command"); } if (_repository == null) { throw new InvalidOperationException("Repository is not initialized."); } var aggregate = new DiaryItem(command.Id, command.Title, command.Description, command.From, command.To); aggregate.Version = -1; _repository.Save(aggregate, aggregate.Version); }}
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.
命令处理程序执行以下任务:
- 从messaging infrastructure(命令总线)接收命令实例。
- 验证该命令是否是一个有效的命令
- 命令的目标是聚合实例(aggregate instance)。
- 从command对象中传递任意参数调用作用在聚合实例上的适当的方法。
- 存储aggregate 的新状态继续存在。
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.
内部事件
我们的第一个问题应该问什么是域事件。域事件是指在系统中过去已经发生的一些事件。事件通常是一个命令的结果。例如,客户端请求一个DTO做出一些的变化,导致被公布在命令中。然后相应的命令处理程序加载正确的Aggregate root并执行适当的行为。这种行为引发一个事件。此事件是由特定的用户处理的。Aggregate 发布事件到事件总线并为该事件分派适当的处事件。这是中内部操作聚合根的事件被称为内部事件。该事件处理程序不应该做任何逻辑替状态设置。
Domain Behavior
域行为
public void ChangeTitle(string title){ ApplyChange(new ItemRenamedEvent(Id, title));}
Domain Event
域事件
public class ItemCreatedEvent:Event{ public string Title { get; internal set; } public DateTime From { get; internal set; } public DateTime To { get; internal set; } public string Description { get;internal set; } public ItemCreatedEvent(Guid aggregateId, string title , string description, DateTime from, DateTime to) { AggregateId = aggregateId; Title = title; From = from; To = to; Description = description; }}public class Event:IEvent{ public int Version; public Guid AggregateId { get; set; } public Guid Id { get; private set; }}
Internal Domain Event Handler
内部域事件处理程序
public void Handle(ItemRenamedEvent e){ Title = e.Title;}
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.
事件通常是连接到另一个叫做 Event Sourcing(ES)模式。ES是一种通过事件流保持保存aggregate状态,以记录更改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.
正如我前面提到的,的每一个Aggregate Root状态的变化都是由事件触发的内部事件操作Aggregate Root除了设置正确的状态没有其他作用。为了得到一个Aggregate Root的状态,我们不得不重播所有内部的事件。在这里我必须提到,事件是只写的。你不能改变或删除现有的事件。如果您发现您的系统中产生一些逻辑错误的事件,您必须生成一个新的补偿事件,纠正以前的错误事件的结果。
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.
外部事件
外部事件通常用于报告数据库当前同步域的状态。他所做的就是发布内部事件到外域。当事件被发布时相应的事件处理程序处理该事件。外部事件可以发布到多个事件处理程序。事件处理程序执行以下任务:
- 从messaging infrastructure收到一个事件实例(事件总线)。
- 事件的目标是定位进程管理器实例。
- 进程管理器实例从事件中传递何任意参数调用适当的方法。
- 继续存储进程管理器的新状态。
可是,是谁发布的事件的?通常情况下,领域仓库负责外部事件发布。
刚浏览下这书,感觉蛮不错的,比《Pro asp.netMVC3》质量要好很多,没学过的童鞋可以每天学习1到2章,看不懂的问题在群论坛留言,大家积极参与,以后群里就以MVC为主要话题。