1、分离查询命令 Separating commands from queries 数据库
早期的面向DDD设计方法的难点是如何设计一个类,这个类要包含域的方方面面。一般来讲,任务软件系统方法调用能够分为两类:查询和命令。在这里,查询是指一个系统的和个操做,它不会改变系统的任务值,仅返回一些结果。命令的职责是个性系统数据。 若是两组方法都使用相同的域模型,逻辑上可能存在查询和命令分离不明显问题,因此引入新的设计模式。 后端
从某种程序度上,CQRS是复杂的域模型设计的一种横向设计思惟。若是域模型客观上就是复杂的,咱们使用CQRS是否还须要? 从目前的设计理念看来,CQRS使用的是两个不一样的域模型,而不是一个。分离是经过将查询和命令分离到两个层上,它们各有本身的架构体系和服务集合。 设计模式
命令和查询的简单对系统的设计影响可能让人惊讶。系统的组织做为两个并行分支,如图上图 所示的体系结构,有一个正式的域模型是系统的严格要求。 缓存
在提供查询服务的时候咱们可能不须要域模型,查询仅仅是用记接口用记查询数据的方法。查询的结果中有可能有命令,因此域模型中设计的各类关系和约束都是不必的。域模型的查询区域专门定制简单的数据传输类DTO作为数据载体。这种状况下,域服务可能成为使用弱类来实现业务有的某些功能。 网络
不一样 于DDD,CQRS 不是企业级系统设计的综合方法。CQRS是为你在大型系统中为上下文边界设计的一个指导模式。DDD设计基本统一语言,贯穿于整个系统设计。 架构
-简单化设计 Simplifcation of the design dom
在域模型交互设计中,一般要对面系统的复杂点是改变系统状态的操做。命令须要验证当前状态并决定是否执行,命令同步要保持系统数据的一致性。在读写共享相同的数据实体时很难确保一个操做不会作出意料以外的读写操做。早期咱们就认识到模型的命令复杂度以笛卡尔积式增加的。称N为查询和命令的复杂度。在单个域模型中,在查询的规定和约束影响命令和反之亦然,如同笛卡尔积,复杂度的成长为N*N。若是将查询和命令分离,模型的复杂度则为N+N。 异步
-加强可扩展性的潜力 Potential for enhanced scalability ide
可扩展性有不少方面的因素,针对性解决方法是保持每个系统的惟一性。一般,可扩展性指系统的可维护性以及在用户增加数量级下的性能。架构 的可扩展性取决于大多数方法的操做类型,若是读取是主要的操做类型,能够引入缓存完全解决数据库的读取压力。若是是写操做将系统拖慢,应考虑使用异步写替代同步,或使用队列。读写分离后,对系统的可扩展性能够更容易、更有针对性的处理。 性能
CQRS实际上没有什么负面影响,如何使用CQRS取决你如何理解CQRS,目前来讲它只用于一种模式,在两个不一样的层,一个是查询服务层一个是命令服务层。系统中全部的部分就会由CQRS带来益处,而且不须要太多的学习成本。
只读域模型 The read domain model
一个只用于读的域模型要比读写兼备的域模型简单的多。有下面一个问题,一个Order类中有一个产品类项列表属性。该属性本质上包含可枚举的数据,但不知道Items应该是哪一种类型,第一种方法是使用泛型IList<T>,这种方法能够实现:
public IList<OrderItem> Items { get; private set; }
使用ReadOnly属性是更好的先择。Read-only不容许更改集合的结构,此外,若是做为包装器,用于常规的列表建立只读集合,则对基础列表的更改不影响只读包装
public class Order
{
private readonly IList<OrderItem> _items;
public Order()
{
_items = new List<MOrderItem>();
}
public ReadOnlyCollection<OrderItem> Items
{
get
{
return new ReadOnlyCollection<OrderItem>(_items);
}
}
public void Add(int id, int quantity)
{
_items.Add(new OrderItem(id, quantity));
}
}
public class OrderItem
{
public OrderItem(int id, int quantity)
{
Quantity = quantity;
ProductId = id;
}
public int Quantity { get; /*private*/ set; }
public int ProductId { get; /*private*/ set; }
}
设计只读模型
查询堆栈可能仍然须要域服务从存储中提取数据,并为它服务达应用程序和表示层。在这种状况下,域名服务和专门的存储库,应将重定向容许只读取的操做在存储上。在这种状况下,域名服务和专门的存储库应将重定向容许只读取的操做在存储上。
……………………………………
的CQRS场景下,Command是的惟一做用就是改变系统的数据。一般应用层接受来处表现层的数据并执行。命令是针对后端,如注册一个新用户、 处理购物车的内容或更新的客户配置文件等数据落地操做。CQRS 的角度来看,命令就是数据持久化的单向操做。
任务有两种方式被触发,一种是用户在UI上明确的开始一项任务,别一种是由系统的一些服务自动触发的任务。命令的主要任务是更新系统数据,但有时候调用都须要返回一些数据来确认调用是否成功。
有两种类型的消息:命令和事件。两种类型中命令是一种数据包,命令是系统执行请求的必要数据。它们有相同点也有不一样点
命令由调用者直接发出
命令能够被系统驳回
命令可能会执行失败
基于网络的命令会依赖系统的当前状态
事件不能由系统驳回或取消
事件能够有多外处理者
The processing of an event can, in turn, generate other events for other handlers to process.
An event can have subscribers located outside the bounded context from which it originated
事件类写法,以下面的代码,命令和事件都继承自Message类。
public class CheckoutCommand : Message
{
public string CartId { get; private set; }
public string CustomerId { get; private set; }
public CheckoutCommand(string cartId, string customerId)
{
CartId = cartId;
CustomerId = customerId;
}
}
Conversely, here's the layout of an event class.
public class DomainEvent : Message
{
// Common properties
...
}
public class OrderCreatedEvent : DomainEvent
{
public string OrderId { get; private set; }
public string TrackingId { get; private set; }
public string TransactionId { get; private set; }
public OrderCreatedEvent(string orderId, string trackingId, string transactionId)
{
OrderId = orderId;
TrackingId = trackingId;
TransactionId = transactionId;
}
}
命令与事件处理
命令由一个被称为Command bus的处理者来管理。事件由Event bus组件来管理。有此时候命令和事件由同一个bus来处理。下图是基于一个事件的CQRS解决方案。全部的任务都是由用户接口发起,在Asp.net MVC中Controller中的Action接收请求并向应用层发起命令。
Bus 组件
Command Bus持有一系统已知业务处理器,这些处理器能够有命令来触发。事件的处理同时会在域中产生许多事件。生成的事件被发布到同一个命令bus或Event bus。Comand Bus是一个接收消息而且找出执行方法的单独的类,Bus类不会本身执行实际要处理的任务,它会选择一个已注册的处理者来处理事件或命令。
public interface IHandles
{
void Handle(T message);
}
接口同时处理命令和事件
public class Bus
{
private static readonly Dictionary<Type, Type> SagaStarters =
new Dictionary<Type, Type>();
private static readonly Dictionary<string, object> SagaInstances =
new Dictionary<string, object>();
public static void RegisterSaga<TStartMessage, TSaga>()
{
SagaStarters.Add(typeof(TStartMessage), typeof(TSaga));
}
public static void Send<T>(T message) where T : Message
{
// Publish the event
if (message is IDomainEvent)
{
// Invoke all registered sagas and give each
// a chance to handle the event.
foreach (var saga in SagaInstances)
{
var handler = (IHandles<T>)saga;
if (handler != null)
handler.Handle(message);
}
}
// Check if the message can start one of the registered sagas
if (SagaStarters.ContainsKey(typeof(T)))
{
// Start the saga creating a new instance of the type
var typeOfSaga = SagaStarters[typeof(T)];
var instance = (IHandles<T>)Activator.CreateInstance(typeOfSaga);
instance.Handle(message);
// At this point the saga has been given an ID;
// let's persist the instance to a (memory) dictionary for later use.
var saga = (SagaBase)instance;
SagaInstances.Add(saga.Data.Id, instance);
return;
}
// The message doesn't start any saga.
// Check if the message can be delivered to an existing saga instead
if (SagaInstances.ContainsKey(message.Id))
{
var saga = (IHandles<T>)SagaInstances[message.Id];
saga.Handle(message);
// Saves saga back or remove if completed
if (saga.IsComplete())
SagaInstances.Remove(message.Id);
else
SagaInstances[message.Id] = saga;
}
}
}
Bus的功能就是作命令映射和事件分发。
Saga组件
通常状况下,一个Saga组件看起来像逻辑上相关的方法和事件处理程序的集合。每一个Saga是一个组件,它声明了如下信息: