此文翻译自msdn,侵删。
原文地址:https://msdn.microsoft.com/en-us/library/dn568103.aspx
通过使用不同的接口来分离读和写操作,这种模式最大化了系统的性能,伸缩性和安全性;能够提供更大的灵活性以支持系统的扩展升级;并且能够防止领域内的更新操作造成的冲突。
在传统的数据管理系统中,commands (更新数据)和queries (查询数据)都依赖仓储(repository)中的一些相同实体。在传统的数据库,例如SQL Server中,这些实体可能只是一张或多张数据表中的一部分。
特别的,在这些系统中,所有的增删改查(CRUD)操作都依赖相同的实体。例如,一个用户从数据获取层(DAL)获得一个包含一个消费者信息的数据传输对象(DTO),然后展示到屏幕上。这个用户更新了这个DTO的某些字段,然后将这个DTO通过DAL层保存起来。相同的DTO被用来进行读和写操作,如图1所示。
图1-一个传统的CRUD架构
传统的CRUD设计在数据操作的业务逻辑比较简单的时候能够工作得很好。一些开发工具提供的代码生成功能(Scaffold mechanisms )能够很快地生成数据类,然后可以根据需要修改这些代码。
然而,这种传统的CRUD方式有一些缺点:
读写职责分离模式(CQRS)是一种把查询(Queries) 数据和和更新(Commands) 数据通过使用各自独立的接口分开的模式。这就意味着用来查询和更新的数据模型是不一样的。这些模型可以被各自分离独立,就像图2展示的那样,尽管这个要求不是一个绝对的。
图2 - 一个基本的CQRS架构
对比于传统的基于CRUD模式的单独数据模型,使用基于CQRS的读写分离模型明显地简化了设计和实现。然而,读写分离模式有一个缺点,那就是不能使用代码生成器自动生成代码。
用于读的模型和用于写的模型可能都来自于同一个数据源,也许是使用SQL视图,也可能是简单的数据映射。无论怎么样,一种常见的做法是将读写数据分离储存在不同的物理存储系统中来使系统的性能,伸缩性和安全性最大化,就像图3展示的那样。
图3 - 一个使用分离读和写数据库的CQRS架构
用于读的数据库可以是一个用于写的数据库的简单复制,它是只读的。或者读和写数据库采用各自的结构。使用只读的“读”数据库集群可以有效地提升读取性能和用户界面的响应速度,尤其是在分布式场景中。一些数据库系统,比如SQL Server,额外地提供了一些机制,比如故障切换,来支持这种模式。
分离读写数据库同时可以实现数各自的负载均衡,例如,“读”的数据库要比“写”的数据库承担更多的负载。
如果一个读写模型包含一些格式化的信息(参见物化视图模式),在从视图中获取数据或者从系统中获取数据的时候,性能就能够得到保证。
更多关于CQRS模式的信息和它的实现,可以参考以下资源:
当考虑如何实现这个模式的时候,需要考虑以下几点:
这种模式在以下场景中适用:
这种模式在下面几个场景中不适用:
This pattern is ideally suited to:
This pattern might not be suitable in the following situations:
CQRS经常和事件溯源模式结合使用。基于CQRS的系统使用分离的读和写模型,每一个都对应相应的任务并且一般储存在不同的数据库中。当和事件溯源模式一起使用的时候,一系列的事件存储相当于“写”模型,是所有信息的可信赖来源(authoritative source )。基于CQRS的系统的读模型提供了数据的物化视图,经常是一种高度格式化的视图形式。这些视图对应相应的界面并且展示了应用程序的需求,帮助最大化展示和查询效率。
使用一系列的事件当作“写”而不是某一个时间点的数据,避免了更新的冲突并且最大化性能和系统的伸缩性,这些事件可以被异步地产生被用来展示数据的物化视图。
因为事件数据库是所有信息的可信赖来源,当系统改进的时候,有可能删除物化视图并且展示所有过去的时间来产生一个新的数据,或者当读模型必须改变的时候。物化视图是一个长久的数据缓存。
当将CQRS和事件溯源模式结合起来的时候,考虑以下几点:
The CQRS pattern is often used in conjunction with the Event Sourcing pattern. CQRS-based systems use separate read and write data models, each tailored to relevant tasks and often located in physically separate stores. When used with Event Sourcing, the store of events is the write model, and this is the authoritative source of information. The read model of a CQRS-based system provides materialized views of the data, typically as highly denormalized views. These views are tailored to the interfaces and display requirements of the application, which helps to maximize both display and query performance.
Using the stream of events as the write store, rather than the actual data at a point in time, avoids update conflicts on a single aggregate and maximizes performance and scalability. The events can be used to asynchronously generate materialized views of the data that are used to populate the read store.
Because the event store is the authoritative source of information, it is possible to delete the materialized views and replay all past events to create a new representation of the current state when the system evolves, or when the read model must change. The materialized views are effectively a durable read-only cache of the data.
When using CQRS combined with the Event Sourcing pattern, consider the following:
下面的代码展示了从一个CQRS实现中截取的部分代码,这段代码使用不同的读写模型。模型的接口并没有表名任何对应数存储的特性,并且他们可以被改进和独立地细致调整因为他们的接口是分开的。
下面的代码展示了读模型的定义:
1 // Query interface 2 namespace ReadModel 3 { 4 public interface ProductsDao 5 { 6 ProductDisplay FindById(int productId); 7 IEnumerable<ProductDisplay> FindByName(string name); 8 IEnumerable<ProductInventory> FindOutOfStockProducts(); 9 IEnumerable<ProductDisplay> FindRelatedProducts(int productId); 10 } 11 12 public class ProductDisplay 13 { 14 public int ID { get; set; } 15 public string Name { get; set; } 16 public string Description { get; set; } 17 public decimal UnitPrice { get; set; } 18 public bool IsOutOfStock { get; set; } 19 public double UserRating { get; set; } 20 } 21 22 public class ProductInventory 23 { 24 public int ID { get; set; } 25 public string Name { get; set; } 26 public int CurrentStock { get; set; } 27 } 28 }
系统允许用户给商品打分,代码通过使用RateProduct方法来实现。
1 public interface Icommand 2 { 3 Guid Id { get; } 4 } 5 6 public class RateProduct : Icommand 7 { 8 public RateProduct() 9 { 10 this.Id = Guid.NewGuid(); 11 } 12 public Guid Id { get; set; } 13 public int ProductId { get; set; } 14 public int rating { get; set; } 15 public int UserId {get; set; } 16 }
系统使用ProductsCommandHandler类去处理应用程序发送的“写”动作。用户通过通讯模块常以队列的形式向领域发送“写”的请求。处理句柄就接受请求然后调用领域的接口方法。每一个请求的粒度都可以被精心设计以缓解冲突。下面的代码展示了一个大致的ProductsCommandHandler类大致的样子。
1 public class ProductsCommandHandler : 2 ICommandHandler<AddNewProduct>, 3 ICommandHandler<RateProduct>, 4 ICommandHandler<AddToInventory>, 5 ICommandHandler<ConfirmItemShipped>, 6 ICommandHandler<UpdateStockFromInventoryRecount> 7 { 8 private readonly IRepository<Product> repository; 9 10 public ProductsCommandHandler (IRepository<Product> repository) 11 { 12 this.repository = repository; 13 } 14 15 void Handle (AddNewProduct command) 16 { 17 ... 18 } 19 20 void Handle (RateProduct command) 21 { 22 var product = repository.Find(command.ProductId); 23 if (product != null) 24 { 25 product.RateProuct(command.UserId, command.rating); 26 repository.Save(product); 27 } 28 } 29 30 void Handle (AddToInventory command) 31 { 32 ... 33 } 34 35 void Handle (ConfirmItemsShipped command) 36 { 37 ... 38 } 39 40 void Handle (UpdateStockFromInventoryRecount command) 41 { 42 ... 43 } 44 }
下面的代码展示了“写“模型的ProductsDomain 的接口。
1 public interface ProductsDomain 2 { 3 void AddNewProduct(int id, string name, string description, decimal price); 4 void RateProduct(int userId int rating); 5 void AddToInventory(int productId, int quantity); 6 void ConfirmItemsShipped(int productId, int quantity); 7 void UpdateStockFromInventoryRecount(int productId, int updatedQuantity); 8 }
同样需要注意的是ProductsDomain接口包含的方法对领域是有意义的。传统的CRUD环境中这些方法的名字都只是的类似于”save“或者”update“这样泛泛的名字,然后有一个DTO作为唯一的参数。比起传统的CRUD模式,CQRS方法可以更好地适应这样一个业务和存货管理系统。
在实现这种模式的时候,以下的模式和指南或许也与之相关:
CQRS读写职责分离模式(Command and Query Responsibility Segregation (CQRS) Pattern)
原文:http://www.cnblogs.com/balavatasky/p/6085814.html