事件溯源和何时执行验证

Event Sourcing and when to perform validation

我正在学习分布式系统中的事件溯源和 CQRS,我在尝试找出执行验证的最佳时间时遇到了一些麻烦......在事件之前或之后已存储?我已经对这个主题进行了大量的搜索和阅读,但我似乎无法找到解决这个问题的 answer/suggestion。

例如(简单的例子),如果我有一个 Web API 请求从银行账户中提取一些钱,我可能会执行以下验证:

  1. 银行账户是否存在?
  2. 银行账户是否有足够的资金提取?

当请求进来时,我是在执行上述验证之前保存事件(并冒着存储无效事件的风险)还是在验证之后(并冒着在过程中出现问题的风险,比如服务停止运行) ,并且根本不存储事件)?在 CQRS 的情况下,事件是在命令执行之前存储的还是作为命令的一部分(在命令处理程序中)?

我很感激在发出请求之前会执行一些验证(例如有效提款金额),但可能会出现在发出请求之前无法进行某些验证的情况。

这也导致我如何才能在 Web API 调用的响应中 return 出现错误(例如,银行帐户无效)?

我对这个主题的理解可能完全错误,但正如我之前提到的,我只是在学习这个主题,我希望有人有答案,或者可以指出一些 posts/articles ,这将有助于我的理解。

事件是事实陈述,无法更改。它们代表实际发生的事情。

您可以在命令导致一系列事件之前对其进行验证。

既然你提到了银行账户,很多时候银行不会限制你透支你的账户。他们只是添加了一个新事实,表示提款导致的透支费用。此场景涉及对取款事件的反应,而不是事件发生前的验证。

一般来说,处理事物的有用方法是将事件定义为处理不会失败的事件(您可以通过忽略事件来处理它,但它永远不会失败)。另一方面,命令可能会失败,或者导致零个或多个事件。

因此,没有事件验证,只有命令验证。在银行账户示例中,您可以在可用资金不足的情况下拒绝提款命令,或者它可能导致提款事件和透支事件(例如,如果由此产生的透支金额在政策范围内)。

一个组件的(我故意避免使用“服务”这个词)的事件可以是另一个组件的命令。

顺便说一句,持久化处理可能失败的对象是完全有效的,但这是一种与事件源相关的技术,称为命令源;命令源和事件源通常可以有效地结合使用,尤其是当命令是其他组件的事件时。

来自Eventuous docs

一般来说,命令处理流程可以这样描述:

  1. 边缘通过其 API(HTTP、gRPC、SignalR、消息传递等)接收命令。
  2. 它将命令传递给应用程序服务。由于边缘负责身份验证和一些授权,因此它可以使用用户凭据丰富命令。
  3. 与 API 本身无关的命令服务处理命令并对边缘做出响应(正或负)。
  4. API层然后returns响应调用方。

命令服务本身在处理一个命令时执行以下操作:

  1. 如有必要,从命令中提取聚合 ID。
  2. 实例化所有必要的值对象。如果无法构造值对象,这可以有效地拒绝命令。命令服务还可以加载一些其他聚合或任何其他信息,这些信息是执行命令所需的但不会更改状态。
  3. 如果命令希望对现有的聚合实例进行操作,则该实例将从聚合存储中加载。
  4. 使用命令中的值和构造的值对象对加载的(或新的)聚合执行操作。
  5. 聚合要么执行操作并通过生成新事件更改其状态,要么拒绝操作。
  6. 如果操作成功,服务会将新事件保存到存储中。否则,就returns一个失败的边缘。

我还会将域模型不变量和验证分开。当我检查提供的字符串值是否确实是银行帐号、有效的 phone 号码、必填字段等时,我使用“验证”一词。域不变量,包括聚合不变量(可以处理取款)和交叉聚合不变量(一个客户不能拥有十个以上的银行账户,真的无法想象其他任何事情)。

如果给定帐户不存在,为其加载聚合将简单地失败。然而,聚合本身可以回答如下问题:

  • 给定用户可以从此帐户中提取资金
  • 账户是否活跃,未被封锁
  • 取款金额是否在该时间段允许的取款限额内

经验提示 - 银行很少关心您在创建付款时账户上是否有钱。它只在付款被执行时才重要,这几乎不会实时发生。