为什么 CQRS 命令处理程序排除保存 UnitOfWork?

Why do CQRS command handlers exclude saving UnitOfWork?

我一直在查看不同的 CQRS samples,其中大多数使用不保存 UnitOfWork 的命令处理程序(即 DataContext,如果是 Entity Framework)。像这样:

    public void Handle(Command message)
    {
        var course = Mapper.Map<Command, Course>(message);

        _db.Courses.Add(course);
    }

保存(和事务提交)通常在处理请求时在后台发生。

我从许多领先的 CQRS 专家那里看到过这种方法,但我从未听过它的推理。

这种方法的最大问题是您需要在返回处理程序调用后立即获取实体 Id 的情况(这种情况经常发生)。显然有解决方法(即使用 Guid、从数据库预先请求唯一 ID 等)但看起来很笨拙。

但是这种方法的优点是什么?从理论上讲,如果我们每个请求有多个处理程序,那么不进行多次数据库往返可能会有所帮助。但这种情况并不多见。我想到的另一个优点是我们不必键入例程 Save 调用并让它自动发生。这有点不错,但它是否加重了 Id 生成问题?

命令处理程序的一个重要特征是它不会 return 任何东西(例外情况除外)。这是一个微妙的问题,但如果您知道所有命令处理程序都不 return 任何东西,那么您可以节省大量代码并简化和/或构建更健壮的代码库。

但是如果您没有 returning 任何东西,那么您会遇到在进程开始时需要 id 的情况。我认为使用 GUID 是一个很好的解决方案。

Pre-fetching 来自数据库的唯一 ID 充满了问题,我会尽可能避免这种方法。

以下是一些优点:

  1. 所有命令处理程序都有一个通用接口
  2. 因此您可以编写支持所有命令处理程序的帮助程序代码。例如命令路由器、安全和权限检查、日志记录、性能监控、消息队列....
  3. 它可以显着减少您需要编写的代码量
  4. 它可以显着降低您需要编写的代码的复杂性
  5. 测试更容易、更可靠

这些只是我头脑中的一些。

为什么不包括一个工作单元?

您可以是简短的回答。但我喜欢将持久化的责任移出处理程序。处理程序可能会调用它,但实际的持久化是在其他地方完成的(在大多数情况下,我的偏好是在事件存储中)。

Why do CQRS command handlers exclude saving UnitOfWork?

我想是从 Evans 开始的,Domain Driven Design,第 6 章 领域对象的生命周期

There is a raft of techniques for dealing with the technical challenges of database access....

But even so, take note of what has been lost. We are no longer thinking about concepts in our domain model. Our code will no be communicating about the business, it will be manipulating the technology of data retrieval.

想法是,在代码的这一点上,我们正在使用 域模型 而不是查看 数据模型 .

A REPOSITORY lifts a huge burden from the client, which can now talk to a simple, intention-revealing interface, and ask for what it needs in terms of the model

埃文斯非常强调这一点,在交易的背景下

Leave transaction control to the client. Although the REPOSITORY will insert into and delete from the database, it will ordinarily not commit anything. It is tempting to commit after saving, for example, but the client presumably has the context to correctly initiate and commit units of work. Transaction management will be simpler if the REPOSITORY keeps its hands off.

一些补充说明:

The biggest problem of this approach are the cases when you need to get an entity Id right after the handler call is returned (which happens quite a lot).

这通常表明您正在解决错误的问题。参见 Marc de Graauw,Nobody Needs Reliable Messaging