CQRS是否设计过度?


15

我仍然记得存储库过去的美好时光。但是随着时间的流逝,存储库变得丑陋。然后,CQRS成为主流。他们很好,呼吸着新鲜空气。但是最近,我又一次又一次地问自己,为什么我不能在Controller的Action方法中保持正确的逻辑(尤其是在Web Api中,Action本身就是某种命令/查询处理程序)。

以前,我对此有一个明确的答案:我这样做是为了进行测试,因为很难使用所有这些不可模仿的单例和整体难看的ASP.NET基础结构来测试Controller。但是时代变了,如今,ASP.NET基础结构类对单元测试的友好程度更高(尤其是在ASP.NET Core中)。

这是一个典型的WebApi调用:添加了命令,并通知SignalR客户端有关此命令:

public void AddClient(string clientName)
{
    using (var dataContext = new DataContext())
    {
        var client = new Client() { Name = clientName };

        dataContext.Clients.Add(client);

        dataContext.SaveChanges();

        GlobalHost.ConnectionManager.GetHubContext<ClientsHub>().ClientWasAdded(client);
    }
}

我可以轻松地对其进行单元测试/模拟。而且,借助OWIN,我可以设置本地WebApi和SignalR服务器并进行集成测试(顺便说一下,速度非常快)。

最近,我越来越不愿意创建繁琐的命令/查询处理程序,并且我倾向于将代码保留在Web Api操作中。仅当重复逻辑或逻辑很复杂并且我想隔离它时,我才例外。但是我不确定我在这里做的事是否正确。

在典型的现代ASP.NET应用程序中管理逻辑的最合理方法是什么?什么时候将代码移动到Commands和Queries处理程序是合理的?有没有更好的模式?

更新。我发现这篇关于DDD-lite方法的文章。因此,看来我将代码的复杂部分移至命令/查询处理程序的方法可以称为CQRS-lite。


1
我看不到CQRS如何使事情更具可测试性/对单例/控制器/等没有任何帮助。它们在很大程度上无关。另外,您仍在(计数器?)示例中创建命令,这很奇怪。
guillaume31

如果您使用基础结构代码保留代码(即获取客户端ip地址),则很难进行测试。如果将逻辑隔离到查询/命令中,则容易得多。代码示例有些混乱,因此我对其进行了更新。
SiberianGuy

1
查询和命令没有逻辑。他们是愚蠢的DTO。然后,您将拥有命令/查询处理程序,但它们与应用程序服务,交互器,业务服务完全相同,在非CQRS上下文中将其命名为...。将应用程序/用例逻辑放在不同于Controller的单独对象中不是CQRS特定的事情。
guillaume31年

同样,单例与注入依赖性的问题与CQRS完全正交。您可能有一个控制器调用CommandHandler单例,该单例调用存储库单例...
guillaume31

1
@ guillaume31,CQRS往往比存储库/复制服务调用更麻烦。他们通常需要2-3个类(即查询,queryhandler,queryresult),基础结构等。–
SiberianGuy

Answers:


17

CQRS是相对复杂且成本高昂的模式吗?是。

工程过度吗?绝对不。

马丁·福勒(Martin Fowler)谈论CQRS原始文章中,您会看到很多关于在不适用的情况下不使用CQRS的警告:

像任何模式一样,CQRS在某些地方很有用,但在其他地方则没有用。

CQRS对于所有相关人员而言都是一次重大的精神飞跃,因此除非收益值得实现,否则不应该解决

尽管我成功地使用了CQRS,但到目前为止,我遇到的大多数情况都不是那么好 ...

尽管有这些好处,但您在使用CQRS时应非常谨慎

...将CQRS添加到这样的系统可能会增加相当大的复杂性

我上面的重点。

如果您的应用程序对所有内容都使用CQRS,则不是因为CQRS过度设计,而是您的应用程序。这是解决某些特定问题的绝佳模式,尤其是其中写并发可能是主要问题的高性能/大容量应用程序。

它可能不是整个应用程序,而只是其中的一小部分。

在我的工作中,有一个生动的例子,我们在订单输入系统中使用了CQRS,在这里我们不会丢失任何订单,而且确实出现了高峰,在某些特定的小时内,不同来源同时发出数千个订单。CQRS帮助我们保持了系统的活力和响应能力,同时使我们能够很好地扩展后端系统,以便在需要时(更多后端服务器)更快地处理这些订单,而在不需要时则更慢/更便宜。

CQRS非常适合您有多个参与者在同一组数据中协作或写入同一资源的问题。

除此之外,将解决单个问题的模式扩展到您所有的问题,只会给您带来更多的问题。


其他有用的链接:

http://udidahan.com/2011/04/22/when-to-avoid-cqrs/

http://codebetter.com/gregyoung/2012/09/09/cqrs-is-not-an-architecture-2/


3
同意除“相对复杂和昂贵的模式”部分以外的所有内容。CQRS只是将读取与写入分开。您可以使用胶带,零中间件/复杂的工具来完成此任务,并尽一切努力保持与以前相同的数据库。
guillaume31

@ guillaume31,这很公平。我认为这与最初的问题非常吻合:“在Web Api中,动作本身就是某种命令/查询”。但是,您是否看到过任何未在命令和查询中使用单独类的有效CQRS示例(最好是github)?这就是我尝试查看其他人如何在.NET中实现CQRS时发现的。
SiberianGuy

是的,但这并不是工程过度。这是该模式的基本前提-将查询与写入分开,将读取模型与域模型分开。
guillaume31年

该CQRS的办法可能是在了很多上下文,但是这是一个完全不同的故事。这并不是因为你在Q中错误地将所有的技术细节归因于CQRS的小技巧,而是因为你并不总是需要CQRS带给你的东西,所以是过大的杀手。
guillaume31年

我认为福勒的观点仅在一定程度上有效。SQL视图是CQRS的一种类型,它与我们在一起已有很长时间了,没有人说它们非常复杂。由于CQRS有许多种形式,即事件源预测,因此在某些领域中可能看起来很复杂,而在另一些领域中则可能微不足道。
阿列克谢·齐马列夫

3

CQRS并不是存储库的一对一替代,正是因为它是一种复杂且昂贵的模式,仅在某些情况下才有用。

这是Udi Dahan的优点:

大多数使用CQRS(以及事件源)的人都不应该这样做。

[...]

很遗憾地说,您会在网上看到大多数显示CQRS的示例应用程序在体系结构上都是错误的。我也会非常警惕那些框架,这些框架会指导您走向实体样式的聚合根CQRS模型。

来源

马丁·福勒:

[...] 使用CQRS应该非常谨慎。许多信息系统都很好地符合以读取信息的方式更新的信息库的概念,将CQRS添加到这样的系统可能会增加相当大的复杂性。

来源

最后,格雷格·杨(Greg Young):

CQRS可以称为架构模式。

[...]

这对于理解大多数架构模式不适用于所有地方非常重要。如果您在建筑指南中看到了建筑模式,则可能有问题

[...]

我从使用事件源的人们那里看到的最大失败是,他们试图在任何地方使用它。

来源

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.