Puff体系结构是企业发展领域中的救星。 借助它的帮助,您可以卸载Iron,并行化进程并恢复代码中的顺序。 在开发公司项目时,我们尝试使用CQRS模式。 一切都变得更加逻辑和……更加复杂。 最近,我谈到了在
Panda-Meetup C#.Net会议上必须面对的问题,现在我与大家分享。

您是否注意到公司应用程序的外观? 为什么不能像苹果和谷歌? 是的,因为我们一直都缺少时间。 需求经常更改,其更改的术语通常是“昨天”。 最令人不愉快的是,企业确实不喜欢错误。

为了解决这个问题,开发人员开始将他们的应用程序分为几部分。 一切都从简单开始-有了数据。 当数据分离,客户端分离,逻辑与数据存储在同一位置时,许多人都熟悉该方案。

好轮廓。 最大的DBMS具有对SQL的全功能过程扩展。 关于Oracle的谚语是:“哪里有Oracle,哪里就有逻辑。” 很难争论这种配置的便利性和速度。
但是我们有一个公司应用程序,并且有一个问题:逻辑很难扩展。 而且,装载DBMS容量是不合理的,而DBMS容量已经遭受了提取和更新数据以及琐碎的业务任务的困扰。
好吧,老实说,内置在DBMS中的业务逻辑编程工具对于创建普通的公司应用程序来说是薄弱的。 在T-SQL / PL-SQL中维护业务逻辑是很痛苦的。 OOP语言在公司应用程序中如此广泛地传播并不是没有道理的:C#,Java,您不必举个例子。

这似乎是一个合理的解决方案:我们重点介绍业务逻辑。 她将生活在自己的服务器上,并以她自己的客户端为基础。
此三层体系结构可以改进什么? 架构涉及业务逻辑层,我想避免这种情况。 业务逻辑不想对数据存储一无所知。 UI也是一个独立的世界,其中存在一些并非特定于业务逻辑的实体。
增加层数将有所帮助。 该解决方案看起来几乎是完美的,它具有某种内在的美。

我们有一个DAL(数据访问层)-数据与逻辑分离,通常是使用ORM的CRUD存储库,再加上用于复杂查询的存储过程。 此选项使您能够足够快地发展,并具有可接受的性能。
业务逻辑可以作为服务的一部分,也可以是单独的层。 层之间的交互可以通过传输对象(DTO)进行。
来自UI的请求进入服务,他与业务逻辑进行通信,爬入DAL以访问数据。 这种方法称为N层,具有明显的优势。
每一层都有自己的明显目标,而作为程序员,我们非常喜欢这些目标。 每个具体层仅从事自己的业务。 服务可以水平扩展。 即使对于新手开发人员,该方法也很清楚,一个人很快就会了解系统的工作方式。 当请求从开始到结束时,跟踪所有交互非常容易。
另一个一致性:所有项目子系统都使用相同的数据,您不必担心我们将数据记录在一个地方,而用户看不到另一部分。
夹心蛋糕1. N层
以下是基于这些原理构建的应用程序典型片段的示例。 我们有一个货币需求,在这里我检查了贫血模型。 并且有一个经典的存储库,可以通过ORM使用。

这是一项典型的服务,他们也称为经理。 他使用资源库,接收请求并为客户提供答案。 在此服务中,我们看到了一些困惑:我们有一个处理过程,一个用于UI的过程以及一些内部控制单元的过程,它们相互之间是弱互连的。
这是此服务的典型方法。 例如,注册货币索赔。

我们接收数据,进行一些业务检查。 然后是一个更新,然后进行一些后续操作,例如,发送通知或写入用户日志。
在这种方法中,尽管具有所有优点,但仍然存在问题。 在公司应用程序中,负载通常是不对称的:读操作比写操作多一个或两个顺序。 扩展数据库本身已经存在问题。 当然,这是完成的,甚至借助数据库规模的DBMS,也可以进行分区。 但这很难。 如果使用错误的限定条件或比必要的条件早完成此操作,则分区将失败。
例如,在我们的一个系统中,数据量达到25 TB,出现了问题。 我们自己试图扩大规模,并邀请了一家知名公司的硬汉。 他们看着说:我们需要基地停机14小时。 我们想着说:伙计们,这行不通,企业不会接受。
除了数据库的容量之外,服务和存储库中的方法数量也在不断增长。 例如,在货币索赔服务中,有一百多种方法。 难以维护,合并请求时存在不断的冲突,代码审查较难执行。 而且,如果考虑到流程不同,则由不同的开发人员团队来进行工作,那么跟踪与问题相关的所有更改的任务将变得非常头疼。
油酥2. CQRS
那该怎么办呢? 古罗马发明了一种解决方案:分而治之。

正如他们所说,一切新事物都被遗忘了。 早在1988年,Bertrand Meyer提出了命令式CQS编程的原理-命令-查询分离-用于处理对象。 所有方法显然都分为两种。 第一个-查询-查询,它在不更改对象状态的情况下返回结果。 也就是说,当您查看客户的货币需求时,没有人应该向客户写入数据库,这样一来,请求中就不会有任何副作用。
第二个命令-命令-用于更改对象状态而不返回数据的命令。 也就是说,您订购了一些更改的内容,作为回报,请不要等待一万行的报告。

在这里,读取数据模型与写入模型明显分开。 大多数业务逻辑都适用于写操作。 阅读可以在具体化的表述上进行,或者通常可以在不同的基础上进行。 它们可以通过事件或某些内部服务进行划分和同步。 有很多选择。
CQRS并不复杂。 我们必须清楚地区分更改系统状态但不返回任何内容的团队。 这里的方法可能更平衡。 如果该命令返回执行结果,并不会特别令人恐惧:一个错误或例如所创建实体的标识符,那么这并不构成犯罪。 团队不要处理请求,不要寻找数据并返回业务实体,这一点很重要。
请求-一切都很简单。 它不会改变病情,因此没有副作用。 这意味着,如果我们连续两次调用请求,并且没有其他命令,则两种情况下对象的状态都应保持相同。 这使您可以并行化查询。 有趣的是,工作不需要单独的查询模型,因为 为此,从域模型中提取业务逻辑毫无意义。
我们的CQRS项目
这是我们在项目中想要做的事情:

我们现有的应用程序自2006年以来一直在运行,它具有经典的分层体系结构。 老式但仍在工作。 没有人想要更改它,甚至不知道用什么替换它。 时机到来,实际上需要从头开始开发新的东西。 在2011-2012年,事件采购和CQRS是一个非常时尚的话题。 我们认为它很酷,因此我们可以存储对象的原始状态和导致该事件的事件。
也就是说,我们并没有像以前那样更新对象。 有一个原始状态,在它旁边是已应用的状态。 在这种情况下,它具有巨大的优势-我们可以在历史记录的任何时刻恢复对象的状态。 实际上,不再需要该杂志。 在存储事件时,我们了解发生了什么。 也就是说,不仅客户地址在“地址”单元格中的值已更新,我们还将记录确切的事件,例如客户的举动。
显然,这种方案在接收数据时工作缓慢,因此我们有一个单独的数据库,其中包含可供选择的材料表示。 好,事件同步:状态变化时,每次事件到达都会发生发布。 从理论上讲,一切似乎都很好,但是...我从未见过在高负载下以可接受的业务一致性在生产上完全意识到这一点的人。

如果处理程序和命令/请求分开,则可以进一步开发该方案。 例如,在这里,我们有一个团队-一个注册的货币索赔:其中有一个日期,金额,客户和其他字段。
我们对金钱要求的注册处理者施加了限制,使其只能接受我们的团队(其中TCommand:ICommand)。 只需添加复杂的需求,我们就可以编写处理程序而无需更改旧的处理程序。 例如,首先更新日期,然后记下该值,然后在此处向客户端发送一条通知-所有这些均在每个命令的不同处理程序中编写。
我们怎么引起这一切? 有一个调度员知道所有这些处理程序的存储位置。

调度程序将(例如,通过DI容器)传递给API。 当命令到达时,它只会执行。 他知道容器在哪里,团队在哪里,并执行它们。 与请求-类似。
这样的方案有什么问题:所有交互都不那么明显。 我们在容器中注册的类型上构建层次结构,然后响应我们的命令/请求。 它需要对体系结构进行非常清晰的设计。 具有一个参数的一种方法的任何动作都不再受限制。 您编写命令,编写处理程序,在容器中注册。 开销量正在增加。 在大型项目中,基本导航会出现问题。 我们决定采用更经典的方式。
对于异步通信,使用了Rebus服务总线。

对于简单的任务,这已经绰绰有余。
CQRS使您对代码的处理方式有所不同,专注于流程,因为所有操作都是流程的一部分。 我们为请求分配了一个存储库,分别制作了与处理相关的命令,并分别处理了相关请求。 为了阅读,我们没有使用单独的存储库,我们只是与团队一起使用ORM。

例如,这里是一种丢弃所有多余内容的方法。 在金钱索赔注册小组中,我们注册需求并在已注册金钱索赔的公共汽车上发布事件。

任何对此感兴趣的人都会对此做出反应。 例如,用户身份验证和日志记录将在那里工作。
这是一个示例请求。 一切也变得很简单:我们阅读并交给存储库。

我想单独留在Rebus.Saga。 这种模式允许您将业务交易分解为原子动作。 这样一来,您不仅可以一次阻止全部封锁,还可以逐步封锁。

第一个元素采取行动并发送消息,第二个订户对此做出响应,执行该消息,然后发送其消息,系统的第三部分已经对此做出响应。 如果一切顺利,Saga会生成自己的指定类型的消息,其他订户将对此做出响应。
让我们看看在这种情况下处理金钱索赔的类是什么样的。 一切都清楚了:有命令,有与注册过程有关的请求,还有带有日志的总线。

在这种情况下,只有一个处理程序。 当事件发生并且团队到达注册货币索赔时,他会对此做出响应。 在内部,所有操作都与以前相同,但是唯一的区别是,存在按过程分组。

因此,它变得容易一些,每个文件中的更改更少。
结论
使用CQRS时需要记住什么? 您需要一种更好的设计方法,因为重写过程要复杂一些。 开销很小,类变得更多了,但这并不重要。 该代码的连接性降低了,但是并不是因为CQRS,而是因为过渡到了总线。 但是正是CQRS促使我们使用此事件交互。 代码变得越来越经常添加而不是更改。 有更多的类,但是现在它们更加专业了。
每个人都需要放弃所有内容并大规模切换到CQRS吗? 不,您需要查看哪种方案最适合特定项目。 例如,如果您的子系统使用目录,则不需要CQRS,那么经典的分层方法将提供更简单,更方便的结果。
完整的熊猫聚会聚会表演可在下面找到。
如果您想更深入地研究该主题,则有必要研究以下资源:
CQRS体系结构样式-来自MicrosoftAlexander Bendyu的博客Contoso大学的CQRS,MediatR,AutoMapper等示例-Jimmy BogardCQRS-Martin Fowler着汇流