请参阅以下文章:https://www.codeproject.com/Articles/555855/Introduction-to-CQRS和http://enterprisecraftsmanship.com/2015/04/20/types-of-cqrs/。这是一些代码:
public class CustomerRepository
{
public void Save(Customer customer) { /* … */ }
public Customer GetById(int id) { /* … */ }
public IReadOnlyList<CustomerDto> Search(string name) { /* … */ }
}
它们都将GetByID描述为命令,即在两种情况下,域对象(Customer)都由方法而不是DTO对象(CustomerDTO)返回。为什么是这样? GetByID从数据库返回数据。它应该是一个查询,即返回CustomerDTO,不应该吗?
更新25/09/17
假设我从数据库中检索产品。然后,我想在Product上运行一些方法(更改实例变量),然后将更改保存回数据库。我会这样做:
ProductDTO productDTO = ProductRepository.GetProduct(1);
DomainProduct domainProduct = AutoMapper.Mapper.Map<DomainProduct>(DomainProduct);
domainProduct.RunSomeMethod();
或者这个:
DomainProduct domainProduct = ProductRepository.GetProduct(1);
domainProduct.RunSomeMethod();
代码的第一个片段可以防止写入数据库上的命中(我认为CQRS是为了防止写入数据库上的读取命中)?但是,GetByID也会命中写入数据库。
哪个片段支持CQRS?既?
答案 0 :(得分:1)
在这种情况下,我认为他们正在区分一个返回只读数据的查询,一旦到达UI就永远不会被使用的查询,以及一个(可能)被提取的Customer域对象。支持编辑的目的有一些“更高”的目的。
在某些情况下,您的读取存储最终是一致的,您可能希望从主存储中获取单个Customer对象,这是真实的记录,而您将针对您的读取存储运行查询,而这可能不是已经赶上了所有已发布的变化。
答案 1 :(得分:1)
从第一篇文章:
指挥方
由于读取方已分离,因此只关注域 处理命令。现在域对象不再需要了 暴露内部状态。 存储库只有几个查询方法 除了 GetById 。
它没有告诉你GetById是一个命令。而是GetById是一个方法(用于检索您可以在其上应用命令的聚合)在存储库上,这是命令堆栈的一部分。但它与查询堆栈中的查询无关。就这样。我还没有读完所有内容,但我相信这两篇文章的概念都是一样的。
答案 2 :(得分:1)
一般(和CQS):
这是命令与查询的不同之处。我不明白为什么你把GetByID
方法称为命令。
就CQRS而言,查询确实意味着从读取模型返回数据。但是,查询不包含在存储库中。存储库中的GetById
方法需要在写入端获取域对象。然后,它由命令处理程序操纵,并且更改将保留在写入端。
这些操作与CQRS的查询/读取方无关。
答案 3 :(得分:1)
但是你的标题不正确。GetById
不是这两篇文章中的命令(无论如何它都不能是command
,可能是命令处理程序或命令方法)。但是在命令端使用 。
更新后:
这是正确的:
DomainProduct domainProduct = ProductRepository.GetProduct(1);
domainProduct.RunSomeMethod();
代码的第一个片段可以防止写入数据库上的命中(I 认为CQRS是为了防止写入数据库上的读取命中)? 但是,GetByID也会命中写入数据库。
它可以防止对读取模型进行写入并读取写入模型。
但是,您可以从写入持久性加载写入模型(聚合根)以向其发送命令 - 这是GetProduct(id)
的目的。
在您的情况下,DomainProduct
不应该有任何查询方法(即getter),只有命令方法(即activate()
)。这就是你如何阻止对写模型的读取:没有任何查询方法。 CQRS是CQS应用于所有模型上的任何方法。此限制仅对域实体应用 ;任何其他对象(即存储库)可以在写入端具有查询方法(如GetProduct(id)
)。
答案 4 :(得分:1)
为什么GetByID是命令而不是查询?
是一个查询。
它们都将GetByID描述为命令,即在两种情况下,域对象(Customer)都由方法而不是DTO对象(CustomerDTO)返回。为什么是这样? GetByID从数据库返回数据。它应该是一个查询,即返回CustomerDTO,不应该吗?
返回的数据的形状不会改变该方法是查询的事实。
Bertrand Meyer在描述Command Query Separation
时通常理解查询查询:返回结果,不要改变系统的可观察状态(没有副作用)。
在这种情况下,结果恰好是一个域对象而不是DTO,但它仍然是迈耶意义上的查询。
CQRS对命令和查询具有相同的理解,并将责任分开。不改变系统的可观察状态的用例由“读取模型”处理,并且用例试图通过“写入模型”修改系统的可观察状态。
如果我们伪分割这个分割,结果看起来就像你期望的那样
namespace ReadModel {
public class CustomerRepository
{
public IReadOnlyList<CustomerDto> Search(string name) { /* … */ }
}
}
我们可以在写模型中重复这种方法......
namespace WriteModel {
public class CustomerRespository {
public CustomerDto GetById(int id) { /* … */ }
public void Save(CustomerDto customer) { /* … */ }
}
}
......但我们通常不这样做。 CQRS从Distributed Domain Driven Design演变而来,正如您可能猜到的那样,受到[tag:域驱动设计]的严重影响。 DDD受面向对象风格的强烈影响;修改模型状态的责任应该在域模型(Tell, Don't Ask)内。
因此,在写模型中,我们不从存储库返回状态,而是返回对域模型实体的引用,该实体更新其自身状态以响应命令
namespace WriteModel {
public class CustomerRespository {
public Customer GetById(int id) { /* … */ }
public void Save(Customer customer) { /* … */ }
}
}
应用程序的核心逻辑没有改变
区别在于代码的组织。
DomainProduct domainProduct = ProductRepository.GetProduct(1);
domainProduct.RunSomeMethod();
这是您在CQRS中通常会看到的样式,因为它是您在域驱动设计中通常会看到的样式:应用程序代码不知道如何管理基础数据 - 它只与域模型(存储库和产品)支持的接口。 Evans使用分层架构 - 应用层与域层进行通信,域层与数据库进行通信。
我认为CQRS是为了防止写入数据库上的读取命中
CQRS中的关键思想是您在阅读时使用的对象不是您在编写时使用的对象。
// I'm in a write use case
Product product = productRepository.getProduct(1);
product.changeTheProductState(...);
// I'm in a read use case
ProductView view = productRepository.getView(1);
return view.queryCurrentState();
如果我想要做的就是询问有关产品状态的问题,那么我会从存储库中获取一个连接到 read 数据库的对象,并提出要求。如果读取的次数比写入次数高10倍,那么就可以防止对写入数据库的大量命中。
但是写入仍然连接到 write 数据库(毕竟这是写入持久存储的地方),并且存储库可能需要在开始计算之前刷新其状态的本地副本它会写。