首页 文章

CQRS微服务:如何处理关系/验证?

提问于
浏览
1

Scenario:

  • 我有2个微服务(内部都使用CQRS事件采购)

  • 微服务1管理联系人(=聚合根)

  • 微服务2管理发票(=聚合根)

发票的收件人必须是有效的联系人 .

CreateInvoiceCommand:

{
  "content": "my invoice content",
  "recipient": "42"
}

我现在读了很多次,写边(=命令处理程序)不应该调用读取端 .


考虑到这一点,Invoices微服务必须监听所有 ContactCreatedContactDeleted 事件,以便知道给定的收件人ID是否有效 .

然后我会在Invoices微服务中拥有数千个联系人,即使我知道其中只有少数会收到发票 .


有没有最佳实践来处理这些情况?

3 回答

  • 1

    发票的收件人必须是有效的联系人 .

    所以你需要注意的第一件事 - 如果两个实体是不同聚合的一部分,你就不能真正实现“仅当该实体满足规范时才对这个实体应用更改”,因为该实体可能会在当下发生变化您评估规范和执行写入的那一刻 .

    换句话说 - 您只能在聚合边界上获得最终的一致性 .

    聚合是其自身状态的权限,但是其他一切(例如,命令消息的内容),它几乎必须接受一些外部权限已经检查了数据 .

    你可以在这里采取几种方法

    1)您可以盲目地接受命令中指定的收件人是有效的 .

    2)您可以尝试从某个外部机构(也就是:某些其他聚合的读取模型)验证收件人的有效性,从不受信任的来源接收它并将其提交给域模型 .

    3)您可以盲目接受所述的命令,但将发票视为临时发票,直到确认收件人的有效期为止 . 这意味着在发票上运行第二个命令来验证收件人 .

    注意 - 从模型的角度来看,这些不同的命令是等价的,但是在应用程序层它们不需要 - 您可以将命令的访问权限限制为受信任的源(不要使它成为public api,需要只有可靠来源的授权等) .

    方法#3是最微观的,因为这两个命令可以及时分开 - 您可以在它到达时立即接受CreateInvoice命令,并异步验证收件人 .

    在哪里可以使用方法4),Invoices Microservice有自己的联系人存储,只要有ContactCreated或ContactDeleted事件,它就会更新?然后,两个实体都是同一服务和边界的一部分 . 现在应该可以使事情保持一致,对吗?

    不,你已经让这两个实体成为同一服务的一部分,但问题从来都不是它们在不同的服务中,而是它们在不同的聚合中 - 这意味着我们可以同时改变实体状态,这意味着我们无法确保它们立即同步 .

    如果您希望立即保持一致性,则需要一个以不同方式划分界限的模型 .

    例如,如果发票实体被建模为Contacts聚合的一部分,则聚合可以确保新发票需要有效收件人的不变量 - 域模型使用内存中的状态副本来确认收件人是否有效当我们加载时,写入记录簿会验证记录簿自加载发生以来没有改变 .

    聚合状态的写入是记录簿中的比较和交换;如果某个并发进程使收件人失效,则CAS操作将失败 .

    当然,权衡是对联系人聚合的任何改变也会导致发票失败;使用相同收件人同时编辑不同的发票会离开窗口 .

    聚合是全有或全无的;它们是不可分离的 .

    现在,一个可能是你的发票聚合有一个必须立即与收件人一致的部分,另一部分最终是一致的,甚至是不一致的,是可以接受的 . 在这种情况下,您的目标是重构模型 .

  • 5

    我认为答案取决于你希望系统具有多大的弹性,也就是说,如何处理 Contacts Microservice 下降的情况(没有响应或非常慢) .

    1. You want to be very resilient

    如果 Contacts Microservice 已关闭,您希望能够为某些(可能是大多数)联系人发出发票 . 在这种情况下,您可以监听 ContactCreatedContactDeleted 并维护(最终一致的)本地有效联系人列表;它们应该在这个有界的语境中相应地命名为无处不在的语言,比如 Payers (或类似的东西) . 然后,在Application层中,在构建 CreateInvoiceCommand 时,检查 Payer 是否有效创建命令 .

    2. You don't need to be resilient

    如果 Contacts Microservice 已关闭,则拒绝生成发票 . 在这种情况下,在构建命令时,您向 Invoices Microservice API endpoints 发出请求并验证 Payer 是否有效 .

    在任何情况下,您都会在调度命令之前检查联系人的有效性 .

  • 2

    发票的收件人必须是有效的联系人 .

    这是一个商业规则 . 应该问一个问题,这个商业规则对我的申请意味着什么?谁应该对实施这条规则负责,还是可以分担责任?

    一种可能性是,是的,业务规则是关于发票的,因此发票服务应该负责实施它 .

    但是,业务规则实际上是关于发票的创建 . 而且,您的架构中创建发票的所有者奇怪地不是发票服务 . 原因是命令的名称是 CreateInvoiceCommand .

    让我们考虑一下 - 发票服务永远不会自己创建发票 . 它只是提供了这种能力 . 在此体系结构中,发票创建的实际所有者是命令的发件人 .

    使用这种推理,如果业务规则是说无法针对无效收件人创建发票,则命令发送方有责任确保实现此业务规则 .

    如果发票服务订阅了事件,而不是接收命令,那将是一种非常不同的情况 . 例如,一个名为 WidgetSold 的事件 . 在这种情况下,发票创建的所有者显然是发票服务,因此业务规则将在那里实施 .

    如果用户单击联系人42的创建发票按钮,则用户有责任确保联系人42存在

    对,那是正确的 . 用户的意图是创建发票 . 因此,应该在此时强制执行发票创建的业务规则 . 如何发生这种情况(或者这种情况是否发生)是一个不同的问题 .

    但是如果用户不关心怎么办?然后它会创建一个收件人ID无效的发票 .

    也正确 . 正如您所说,这种方法存在副作用,其中之一就是您最终可能会在系统中出现不一致的数据 . 这是SOA的现实之一 .

    这不是类似于此:Invoice有一个currencyCode属性,它是一个String .

    我不知道我是否同意 . 问这是一种有效的ISO货币吗?不同的是要求实体42根据另一个系统有效吗?我想是的 .

    根据我的联系人数据库,是不是有点相同,因为给定的收件人不是空的并且是有效的?

    我同意,实际上,您可以在服务中实现此验证 . 我只是说我认为它不适合它 . 如果您想这样做,则必须先调出另一项服务或在本地存储所有联系人,因为您最初构建了问题 . 我认为在服务之外做这件事更简单 .

相关问题