事件溯源与CQRS


领域驱动设计

什么是域?

为其构建系统的领域。机场管理、保险销售、咖啡店、轨道飞行,应有尽有。

一个应用程序跨越多个不同的域并不罕见。例如,在线零售系统可能在运输(选择适当的交付方式,取决于物品和目的地)、定价(包括促销和用户特定的定价,例如位置)和推荐(计算相关按购买历史记录的产品)。

什么是模型?

“对手头问题的有用近似。” ——格里·苏斯曼

一个Employee班级不是真正的雇员。它模拟了一个真正的员工。我们知道该模型并不能捕捉到真正员工的所有信息,这不是重点。它只是为了捕捉我们对当前上下文感兴趣的内容。

不同的领域可能对建模同一事物的不同方式感兴趣。例如,工资部门和人力资源部门可能以不同的方式对员工进行建模。

什么是领域模型?

域的模型。

什么是领域驱动设计 (DDD)?

这是一种深入重视领域模型并将其与实现联系起来的开发方法。DDD 由 Eric Evans 创造并最初开发。

大家都在谈论的蓝皮书是什么?

这个?它是 DDD 创始人 Eric Evans 关于领域驱动设计的定义文本。强烈推荐它。

什么是无处不在的语言?

涉及域、域模型、实现和后端的所有人员使用的一组术语。这个想法是避免翻译,因为正如 Eric Evans 指出的那样,

翻译使交流变得迟钝,使知识处理变得乏力。

也就是说,每次我们必须在人与人之间翻译概念时——“哦,在我使用‘帐户’的情况下,你使用的是‘用户’”——我们失去了清晰思考我们正在构建的东西的直接能力并让新知识在领域和实施之间来回流动。

投资一种无处不在的语言是有回报的,因为它使沟通更清晰,让团队看到更多的机会。

什么是有界上下文?

一个更大的系统的一个部门,它有自己的通用语言和领域模型。在线零售商的定价、运输和推荐方面将被视为单独的有界上下文,因为它们具有显着不同的关注点。

与其他 DDD 概念一样,有界上下文在实施时最有价值。

如何识别有界上下文?

一些常见的事情是:

  • 组织中的自然边界(在有界上下文中,您通常会发现人们密切协作和沟通;在有界上下文之间,通信较少,并且通常是异步的)
  • 同一个词被赋予不同的含义(产品到定价是一个有价格的东西;产品到运输是一个有重量和尺寸的东西,等等)

通常,良好的有界上下文看起来像产品(定价策略产品、运输计算产品、产品推荐引擎产品等)。这与产品优于项目的团队结构非常吻合。

有界上下文应该与我的系统的其余部分隔离到什么程度?

相当强烈。一般来说,最好避免直接依赖。例如,在 .Net 中,单独的程序集是相当明智的。在诸如 SOA 或微服务之类的分布式范式中,在有界上下文之间找到进程边界是正常的。

如何在有界上下文之间进行通信?

仅就其公共 API 而言。这可能涉及订阅来自另一个有界上下文的事件。或者一个有界上下文可以像另一个的常规客户端一样发送命令和查询。

什么是实体?什么是价值对象?

实体引用类型的特点是具有与其属性值无关的标识。实体中的所有属性都可以更改,并且它仍然是“相同”的实体。相反,两个实体可能在所有属性上都是等价的,但仍然是不同的。

值对象没有单独的标识;它们仅由它们的属性值定义。虽然我们通常在提到值类型时谈论对象,但本机类型实际上是值类型的一个很好的例子。使值类型不可变是很常见的。例如,String在许多语言中是不可变的,每次您想“更改”一个字符串时,都会派生一个新字符串。

从事件溯源的角度来看,实体和值对象在域中都扮演着重要的角色,但只有实体需要被持久化,因为只有这些会发生变化。

命令和事件

什么是事件?

事件代表在域中发生的事情。它们总是以过去分词动词命名,例如OrderConfirmed. 事件命名与其相关的聚合或实体并不罕见,但不是必需的;让领域语言成为您的指南。

由于事件代表过去的某事,因此可以将其视为事实陈述,并用于在系统的其他部分做出决策。

什么是命令?

人们通过发送命令请求更改域。它们以祈使语气加动词命名,可能包括聚合类型,例如 ConfirmOrder。与事件不同,命令不是事实陈述;这只是一个请求,因此可能会被拒绝。(表达拒绝的典型方式是抛出异常)。

命令或事件是什么样的?

命令和事件只是包含用于读取的数据的数据结构,没有任何行为。我们称这种结构为“数据传输对象”(DTO)。名称表明目的。在许多语言中,它们被表示为类,但它们并不是真正的 OO 意义上的真正的类。下面是一个命令示例:

public class ConfirmOrder {
    public Guid OrderId;
}

这是一个事件的例子:

public class OrderConfirmed {
    public Guid     OrderId;
    public DateTime ConfirmationDate;
}

命令和事件有什么区别?

他们的意图。

什么是不变性?为什么命令和事件是不可变的?

就这个问题而言,不变性没有任何设置器或其他改变内部状态的方法。Java 和 C# 中的字符串类型是一个熟悉的例子。你永远不会真正改变现有的字符串值,你只是根据旧的字符串值创建新的字符串值。

命令是不可变的,因为它们的预期用途是直接发送到域模型端进行处理。他们不需要在从客户端到服务器的预计生命周期内进行更改。

事件是不可变的,因为它们代表过去发生的域操作。除非你是 Marty McFly,否则你无法改变过去,有时甚至无法改变过去。

什么是命令升级?

当新要求导致现有命令不够用时,升级命令变得必要。例如,可能需要添加一个新字段,或者可能确实应该将现有字段拆分为几个不同的字段。

如何升级我的命令?

您如何进行升级取决于您对客户的控制程度。如果您可以同时部署客户端更新和服务器更新,只需更改两者并部署更新即可。任务完成。如果没有,通常最好让更新的命令成为一种新类型,并让命令处理程序接受一段时间。

您能否举例说明一些版本化命令的名称?

当然。

UploadFile
UploadFile_v2
UploadFile_v3

这只是一种约定,但一种理智的约定。

命令/查询职责分离

什么是 CQRS?

CQRS 的意思是“命令查询职责分离”。我们在 命令(写入请求)和查询(读取请求)之间分离**职责。写请求和读请求由不同的对象处理。

而已。我们可以进一步拆分数据存储,拥有独立的读写存储。一旦发生这种情况,可能会有许多读取存储,针对处理不同类型的查询或跨越许多有界上下文进行了优化。尽管经常讨论与 CQRS 相关的单独的读/写存储,但这不是 CQRS 本身。CQRS 只是命令和查询的第一个拆分。

CQRS 听起来像是那些新奇的饮食之一。这个词是谁编的?

格雷格·杨。

多年来,他一直在抱怨搜索引擎天真地问“你是说汽车吗?” 当搜索 CQRS 时。

我听说还有一种叫做 CQS 的东西。它是什么,它与 CQRS 有什么关系?

CQS 的意思是“命令-查询分离”。它是由 Bertrand Meyer 介绍的,作为他在 Eiffel 编程语言工作的一部分。

这意味着方法要么是执行操作的命令,要么是返回数据的**查询 ,但不能两者兼而有之。作为纯粹的动作执行方法,命令总是有一个void返回类型。另一方面,查询不应该对系统本身产生任何可观察到的副作用。

最初,CQRS 也被称为“CQS”。但确定两者的差异足以让 CQRS 拥有自己的名字。主要区别在于:

  • CQS 将命令和查询放在一个类型中的不同方法中。
  • CQRS 将命令和查询放在不同的对象上。

CQRS 可以简化吗?

当然。通用存储库在许多系统中都很常见。它们在 CRUD 场景中运行良好——通常是那些你可能没有应用 DDD 的场景。它们往往适用于创建、更新、删除和读取单个实体。但是一旦有一个跨越多个实体的查询,它应该去哪里?

与其苦恼于它,并试图将查询硬塞到通用存储库安排中,不如将它们放在一个单独的对象上要容易得多。毫无疑问,他们可以返回简单、轻量级的数据 DTO。

CQRS 并不一定意味着进行事件溯源、引入命令、事件、读取面、sagas 等等。

CQRS 不会使我的应用程序更复杂吗?

一个典型的 CQRS + 事件溯源系统似乎有更多的组件,因为命令、事件、异常和查询成为公共接口的一部分。聚合、命令处理程序、读取侧投影、sagas 和客户端进一步促进了组件的扩散。

然而,每个组件都与其他组件巧妙地分离。最初,“复杂”的意思是“编织在一起”。CQRS+ES 系统中的组件是独立的,有利于对系统进行推理并响应不断变化的需求:

  • 消息类型的公共接口构成了应用程序的一层,鼓励您根据用户意图进行思考,而不是更新数据。
  • 系统分为客户端、写入端和读取端,便于在各个团队之间划分工作。
  • 也许最重要的是,测试变得非常自然,即使是业务逻辑中最重要和最复杂的部分。

写入端是否应该始终独立于读取端?

不会。但它通常会有所帮助——例如,通过在写入端使用事件溯源,这可以提供很多好处。

事件溯源

什么是事件溯源?

将所有更改(事件)存储到系统,而不仅仅是其当前状态。

为什么我以前没有听说过?

你有。几乎所有事务 RDBMS 系统都使用事务日志来存储应用于数据库的所有更改。在紧要关头,可以从此事务日志重新创建数据库的当前状态。这是一种活动。事件溯源只是意味着遵循这个想法并使用这样的日志作为主要数据源。

事件溯源有哪些优势?

  • 能够将系统置于任何先前状态。对调试很有用。(即上周系统是什么样的?)
  • 拥有系统的真实历史。提供更多好处,例如审计和可追溯性。在某些领域,这是法律要求的。
  • 我们通过存储所有事件并能够根据需要创建任意读取端预测来减轻无法预测未来需求的负面影响。这允许对新要求做出更灵活的响应。
  • 在事件存储上进行的操作类型非常有限,使得持久性非常可预测,从而简化了测试。
  • 事件存储在概念上比完整的 RDBMS 解决方案更简单,并且很容易从内存中的事件列表扩展到功能齐全的事件存储。

事件溯源是否需要执行 CQRS?

不,您可以以您喜欢的任何形式保存您的聚合。然而,事件溯源与 CQRS 配合得很好,并带来了许多额外的好处。

如果事件队列中的事件被证明是错误的怎么办?

在事件队列中,新事件被添加到队列的末尾。事件永远不会被删除或更改。(顺便说一句,就像在会计师的分类账中一样。)补偿措施是您可以添加的,以纠正实际错误。它们只是抵消早期事件的事件。

使用事件溯源不会让我的系统变慢吗?

不。

应用事件来建立当前状态需要更多时间。但是处理器真的很快。应用事件需要微秒的数量级。对于大多数域,性能不是问题。

此外,与事件溯源密切相关的聚合边界应该会导致系统能够很好地水平扩展。

什么是快照?

一种优化,聚合状态的快照也每隔一段时间(从概念上)保存在事件队列中,以便事件应用程序可以从快照开始,而不是从头开始。这可以加快速度。快照总是可以根据需要丢弃或重新创建,因为它们表示来自事件流的计算信息。

通常,与持久事件的常规任务分开的后台进程负责创建快照。

快照有许多与在数据库中重新引入当前状态相关的缺点。与其假设您将需要它,不如先不做快照,然后在分析表明它会有所帮助后才添加它。

如何版本/升级我的活动?

您将它们原样保留在事件存储中,因为它在概念上是一个仅附加列表。但是,写入方和读取方都可以“升级”其处理程序中的传入事件。一个事件总是可以升级到一个更新的版本……如果不是,它可能毕竟不是一个更新的版本,而是一个完全不同的事件类型。

随着时间的推移,我如何处理不断增长的/大型事件存储?

事件通常很小,您可以轻松地在低端关系数据库上存储、索引和搜索数百万个事件。

也就是说,提前计划总是好的,并选择在大小方面为您提供良好服务的序列化格式。例如,JSON 往往小于相应的 XML。

如果您觉得需要通过算法压缩您的事件,这也是一种选择。Google 的协议缓冲区是要使用的压缩序列化的现代示例。

对于实际上硬盘空间用完的情况:现在磁盘很便宜。考虑将历史事件保存在一些永久存储中。活动具有重要的商业价值;不要把它们扔掉。

如果事件存储超过了单台机器,那么很容易首先按聚合类型进行分片,并且即使在聚合本身的级别上也可以使用少量基于内容的路由。

我也可以坚持命令吗?

记录命令通常很有用,因为它们包含有关在域模型上发出的请求的重要信息。

但是命令不是事件,它们不属于事件存储。只需考虑将命令的日志记录作为围绕命令处理程序的附加方面。

CAP 和最终一致性

什么是 CAP 定理?

CAP 定理指出,在分布式系统中,您可以在给定时间点具有以下三个属性中的两个:

  • 一致性
  • 可用性
  • 分区容错

要理解原因,想象一下当分区两侧的两个节点尝试更新整个系统时会发生什么。

CAP 适用于什么级别?

CAP 是细粒度的。您可以在系统的不同部分做出不同的选择。例如,对于接受订单,通常需要可用性,因为您不想丢失订单!

什么是最终一致性?

不再强调系统中的即时一致性(即所有事物始终具有相同的数据视图),以换取更高的可用性和更大的组件自主权。

消息传递

如何处理重复的命令/事件问题?

在传输层。

发布事件时应该使用推送还是拉取?

推送的优点是事件可以在发生时推送。拉取的优点是读取端可以更加活跃和独立。在我们看来,在读取端使用本地事件缓存拉取似乎是最好的和最具可扩展性的解决方案。但是,Push 可以很好地与反应式编程和 Web 套接字一起工作。同样,您不必在系统中的任何地方都做出相同的选择。

测试

如何测试我的 CQRS 应用程序?

仅使用命令、事件和异常。

什么是行为测试?

纯粹基于对象的行为进行测试,而不讨论其状态。具体来说,这意味着我们只调用方法。这非常适合命令和事件方面的测试,因为应用事件和处理命令是聚合的公共 API 的一部分。

“告诉,不要问”是什么意思?

决策应在数据所在的封装边界内做出。对象或集合是“专家”,外部事物不应该询问它的状态然后为它做出决定。

“告诉,不要问”被认为是面向对象设计的一个很好的原则。

CQRS 应用程序鼓励的测试是“告诉,不要问”的一个很好的例子。测试聚合行为的唯一方法是设置它们(使用事件),告诉它们做某事(使用命令),然后观察结果(更多事件或异常)。

我如何知道命令因正确原因而失败?

使用类型化异常来指示失败的模式,并在测试中排除该类型的异常。

所以我知道我得到了正确的事件,但我怎么知道它意味着什么?

测试给定命令是否导致预期事件只是工作的一半。为了确保事件的应用程序确实有意义,请在历史记录中使用该事件编写测试。例如,要测试一个指示预约的事件是否真正生效,将其放入历史记录并尝试进行冲突预约。

聚合

什么是聚合?

一个比一个类更大的封装单元。每个事务都限定为一个聚合。聚合的组件的生命周期受整个聚合的生命周期的限制。

具体来说,聚合将处理命令、应用事件,并在其中封装一个状态模型,允许它实现所需的命令验证,从而维护聚合的不变量(业务规则)。

聚合和聚合根有什么区别?

聚合形成对象关系的树或图。聚合根是“顶部”的根,它代表整体,并且可以委托给其余部分。这很重要,因为它是世界其他地方与之交流的地方。

我知道聚合是事务边界,但我确实需要在同一个事务中以事务方式更新两个聚合。我应该怎么办?

您应该重新考虑以下问题:

  • 您的总边界。
  • 每个聚合的职责。
  • 在阅读方面或传奇中,您可以逃脱惩罚。
  • 您的域的实际非功能性需求。

如果您编写了一个解决方案,其中两个或多个聚合是事务耦合的,那么您还没有理解聚合。

为什么使用 GUID 作为 ID 是一种好习惯?

因为它们(合理地)是全局唯一的,并且可以由服务器或客户端生成。

如何获取新创建的聚合的 ID?

客户端可以生成自己的 ID,这是一个重要的见解。

如果客户端生成一个 GUID 并将其放置在 create-the-aggregate 命令中,这不是问题。否则,您必须从适当的读取端进行轮询,其中 ID 将出现在最终一致的时间范围内。显然,这比一开始就生成它要脆弱得多。

我应该允许聚合之间的引用吗?

在实际的“内存引用”的意义上,绝对不是。

在写入方面,从一个聚合到另一个聚合的实际内存引用是被禁止和错误的,因为根据定义不允许聚合到达它们自身之外。(允许这意味着聚合不再是事务边界,这意味着我们不能再理智地推断其支持其不变量的能力;它还将排除聚合的分片。)

使用字符串标识符引用另一个聚合很好。它在写入端是无用的(因为标识符必须被视为一个不透明的值,因为聚合无法到达它们自身之外)。然而,阅读方可以自由地使用这些信息来进行有趣的关联。

如何跨一组聚合验证命令?

这是对不再能够跨聚合进行查询的常见反应。有几个答案:

  • 进行客户端验证。
  • 使用读取端。
  • 使用传奇。
  • 如果这些都是完全不切实际的,那么是时候考虑一下你的聚合边界是否正确了。

如何保证跨聚合的引用完整性?

你仍然在考虑外交关系,而不是总体。见最后一个问题。另外,请记住,仅仅因为关系设计中有两个表并不意味着它应该是两个聚合。聚合设计是不同的。

如何确保新创建的用户具有唯一的用户名?

这是一个经常出现的问题,因为我们没有明确地在写入端执行交叉聚合操作。但是,我们确实有多种选择:

  • 创建已分配用户名的读取端。当用户键入名称时,使客户端以交互方式查询读取端。
  • 创建一个反应式传奇来标记和停用仍然使用重复用户名创建的帐户。(无论是极端巧合还是恶意或由于客户端故障。)
  • 如果最终一致性对您来说不够快,请考虑在已分配名称的写入端添加一个表,就像是一个小的本地读取端。使聚合事务包括插入该表。

下订单时如何验证客户 ID 是否真实存在?

假设 customer 和 order 在这里是聚合,很明显 order 聚合不能真正验证这一点,因为这意味着超出聚合。

事后检查它,在传奇中或只是在记录“损坏”订单的读取端,是一种选择。毕竟,关于订单最重要的事情实际上是记录它,并且大概任何关于订单接收者的有趣数据都被复制到订单聚合中(指客户找到地址是糟糕的设计;订单总是交付到特定地址,无论该客户将来是否更改其地址)。

能够使用以这种损坏的顺序记录的数据意味着您有机会拯救它并纠正这种情况 - 这比因为违反外键约束而放弃订单更具商业意义!

如何使用单个命令更新一组聚合?

单个命令不能作用于一组聚合。它就是不能。

首先,问问自己是否真的需要只使用一个命令来更新多个聚合。在这种情况下,这是什么要求?

但是,这是您可以做的。允许一种新的“批量命令”,概念上包含您要发出的命令,以及您要发出它的一组聚合(显式或隐式指定)。写入端的功能不足以进行批量操作,但它能够创建相应的“批量事件”。saga 捕获事件,并对每个指定的聚合发出命令。如果某些命令失败,saga 可以酌情回滚或发送电子邮件。

这种方法有一些优点:我们将批量操作的意图存储在事件存储中。传奇自动回滚或等效。

尽管如此,不得不求助于这个解决方案是一个强烈的迹象,表明您的聚合边界没有正确绘制。您可能需要考虑更改聚合边界,而不是为此构建传奇。

什么是分片?

一种在多个写入端节点上分布大量聚合的方法。我们可以轻松地对聚合进行分片,因为它们是完全自力更生的。

我们可以轻松地对聚合进行分片,因为它们没有任何外部引用。

聚合可以将事件发送到另一个聚合吗?

不。

聚合和命令处理程序的分解通常已经使这个想法无法在代码中表达。但还有一个更深层次的哲学原因:回去重新阅读 “什么是聚合?”的答案中的第一句话。. 如果您设法绕过命令处理程序并以某种方式将事件推送到另一个聚合中,那么您将剥夺该聚合参与更改验证的机会。这最终就是为什么我们只允许在聚合上的命令处理程序验证的命令的结果中创建事件。

我可以从我的聚合中调用读取端吗?

不。

如何在 CQRS 系统中发送电子邮件?

在聚合之外的事件处理程序中。不要在命令处理程序中执行此操作,就好像由于与另一个命令的竞争而导致事件没有持久化,那么电子邮件将在错误的前提下发送。

命令处理程序

命令处理程序做什么?

命令处理程序接收命令并代理来自适当聚合的结果。“结果”要么是命令的成功应用,要么是异常。

这是命令处理程序遵循的常见步骤序列:

  1. 验证命令本身的优点。
  2. 在聚合的当前状态上验证命令。
  3. 如果验证成功,则 0..n 个事件(1 是常见的)。
  4. 尝试持久化新事件。如果在这一步发生并发冲突,要么放弃,要么重试。

命令处理程序是否应该影响一个或多个聚合?

只有一个。

我是否将逻辑放入命令处理程序中?

是的。究竟什么逻辑取决于您的因式分解。

验证命令本身的优点的逻辑总是在命令处理程序中。如果命令处理程序只是聚合上的一个方法,那么下一步就是简单地使用聚合的状态来做进一步的验证。在功能更强大的分解中,聚合独立于命令处理程序存在,下一步是加载聚合并对其进行验证。

如果验证成功,那么命令处理程序应该会产生事件。根据因子的不同,它还可能需要进一步的步骤来尝试和持久化它们。

在 Edument CQRS 入门工具包中,命令处理程序是返回事件的方法。事件的加载、聚合的构建和事件的持久化完全由命令处理程序考虑。这使它们非常干净和专注,因此与持久性机制完全解耦。

不管你有它,逻辑归结为验证和一些导致命令成为异常或事件的步骤序列。如果您想超越这一点,请参阅本节中的其余问题。

我可以从我的命令处理程序调用读取端吗?

不。

我可以在我的命令处理程序中进行日志记录、安全性或审计吗?

是的。装饰器模式在这里派上用场,可以巧妙地分离这些关注点。

命令处理程序中如何处理并发命令之间的冲突?

聚合的新事件被持久化的地方是系统中唯一需要担心并发冲突的地方。事件存储知道应用于该聚合的最新事件的序列号,命令处理程序知道它读取的最后一个事件的序列号。如果这些数字不一致,则意味着其他线程或进程首先到达那里。然后,命令处理程序可以再次加载事件并进行新的尝试。

我应该在命令处理程序中做对外界有副作用的事情(例如发送电子邮件)吗?

不,因为并发冲突将意味着命令处理程序逻辑将再次运行。在事件处理程序中执行此类操作。

读边

什么是读端?

读取端侦听从写入端发布的事件,将这些事件投影为对本地模型的更改,并允许对该模型进行查询。

读边解决了哪些实际问题?

它们使关联模型数据(JOIN用 SQL 术语称为)的成本从每次读取变为每次写入。读取端的查询只是直接的 SELECT,因为数据已经处于客户想要的形状。

这是一个净赢,因为通常情况下,系统中的读写比率通常为 10 或更多。这个想法与 SQL 数据库中的“视图”非常相似。

如果我的域的写入次数多于读取次数怎么办?

确定吗?在做出肯定的答复之前,请确保您进行了测量。

一些域(例如电信)在短期内的写入强度很高,并且对写入端的要求很高。但随后读取端通常会赶上并读取接管。

某些领域(例如实时股票市场)完全由传入的数据控制,并且必须优化写入端以实时应用命令。

什么是投影?

一组协同工作以构建和维护读取模型的事件处理程序。

如果我构建了一个读取端并且预测结果以某种方式错误怎么办?

如果您无法在运行中轻松纠正它,则使用固定投影构建读取端的新版本,部署它,让它重新处理事件存储中的所有事件,以便它使用最新数据,然后切换查询使用它。

Sagas

什么是Sagas?

以交叉聚合、最终一致的方式对域事件作出反应的独立组件。时间也可以是触发器。Sagas 有时纯粹是响应式的,有时代表工作流。

从实现的角度来看,saga 是由传入事件(可能来自许多聚合)驱动的状态机。某些状态会产生副作用,例如发送命令、与外部 Web 服务交谈或发送电子邮件。

Sagas不是泄露了领域逻辑吗?

不。

Sagas 正在做的事情是任何个体聚合都无法明智地做到的。因此,这不是逻辑泄漏,因为无论如何逻辑都不属于聚合。此外,我们不会以任何方式破坏封装,因为 sagas 使用命令和事件进行操作,它们是公共 API 的一部分。

我怎样才能让我的Saga对没有发生的事件做出反应?

除了对领域事件做出反应外,传奇故事还可以被反复出现的内部警报“唤醒”。实现这样的警报很容易。例如,参见cronUnix。

saga 如何与写入端交互?

通过向它发送命令。

偶尔连接的系统

线下客户呢?

客户端可以离线工作,允许您在本地发出命令,在重新连接时与写入端同步。

客户端倾向于引入写入端(用于进行本地验证)和读取端(更新速度超过最终一致性允许的速度)的功能。从某种意义上说,由于客户端是系统的用户窗口,它总是有增长的趋势,直到它看起来像整个系统的一个小副本,包括写端和读端。

什么是命令合并?

有时在高度协作的域中,命令“太晚”到达,并且聚合的当前状态已经改变,因此命令不能干净地应用。命令合并是从命令中提取潜在意图,然后从该意图创建和应用新命令的行为。

在偶尔连接的客户端中,如何在实践中完成命令合并?

git 合并模型似乎很适合窃取。

转自http://cqrs.nu/Faq,更多详情可前往查看
项目示例:https://github.com/looplab/eventhorizon


文章作者: hypo
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hypo !
评论
  目录