Scenario:
-
我有2个微服务(内部都使用CQRS事件采购)
-
微服务1管理联系人(=聚合根)
-
微服务2管理发票(=聚合根)
发票的收件人必须是有效的联系人 .
CreateInvoiceCommand:
{
"content": "my invoice content",
"recipient": "42"
}
我现在读了很多次,写边(=命令处理程序)不应该调用读取端 .
考虑到这一点,Invoices微服务必须监听所有 ContactCreated
和 ContactDeleted
事件,以便知道给定的收件人ID是否有效 .
然后我会在Invoices微服务中拥有数千个联系人,即使我知道其中只有少数会收到发票 .
有没有最佳实践来处理这些情况?
3 回答
所以你需要注意的第一件事 - 如果两个实体是不同聚合的一部分,你就不能真正实现“仅当该实体满足规范时才对这个实体应用更改”,因为该实体可能会在当下发生变化您评估规范和执行写入的那一刻 .
换句话说 - 您只能在聚合边界上获得最终的一致性 .
聚合是其自身状态的权限,但是其他一切(例如,命令消息的内容),它几乎必须接受一些外部权限已经检查了数据 .
你可以在这里采取几种方法
1)您可以盲目地接受命令中指定的收件人是有效的 .
2)您可以尝试从某个外部机构(也就是:某些其他聚合的读取模型)验证收件人的有效性,从不受信任的来源接收它并将其提交给域模型 .
3)您可以盲目接受所述的命令,但将发票视为临时发票,直到确认收件人的有效期为止 . 这意味着在发票上运行第二个命令来验证收件人 .
注意 - 从模型的角度来看,这些不同的命令是等价的,但是在应用程序层它们不需要 - 您可以将命令的访问权限限制为受信任的源(不要使它成为public api,需要只有可靠来源的授权等) .
方法#3是最微观的,因为这两个命令可以及时分开 - 您可以在它到达时立即接受CreateInvoice命令,并异步验证收件人 .
不,你已经让这两个实体成为同一服务的一部分,但问题从来都不是它们在不同的服务中,而是它们在不同的聚合中 - 这意味着我们可以同时改变实体状态,这意味着我们无法确保它们立即同步 .
如果您希望立即保持一致性,则需要一个以不同方式划分界限的模型 .
例如,如果发票实体被建模为Contacts聚合的一部分,则聚合可以确保新发票需要有效收件人的不变量 - 域模型使用内存中的状态副本来确认收件人是否有效当我们加载时,写入记录簿会验证记录簿自加载发生以来没有改变 .
聚合状态的写入是记录簿中的比较和交换;如果某个并发进程使收件人失效,则CAS操作将失败 .
当然,权衡是对联系人聚合的任何改变也会导致发票失败;使用相同收件人同时编辑不同的发票会离开窗口 .
聚合是全有或全无的;它们是不可分离的 .
现在,一个可能是你的发票聚合有一个必须立即与收件人一致的部分,另一部分最终是一致的,甚至是不一致的,是可以接受的 . 在这种情况下,您的目标是重构模型 .
我认为答案取决于你希望系统具有多大的弹性,也就是说,如何处理
Contacts Microservice
下降的情况(没有响应或非常慢) .1. You want to be very resilient
如果
Contacts Microservice
已关闭,您希望能够为某些(可能是大多数)联系人发出发票 . 在这种情况下,您可以监听ContactCreated
和ContactDeleted
并维护(最终一致的)本地有效联系人列表;它们应该在这个有界的语境中相应地命名为无处不在的语言,比如Payers
(或类似的东西) . 然后,在Application层中,在构建CreateInvoiceCommand
时,检查Payer
是否有效创建命令 .2. You don't need to be resilient
如果
Contacts Microservice
已关闭,则拒绝生成发票 . 在这种情况下,在构建命令时,您向Invoices Microservice
API endpoints 发出请求并验证Payer
是否有效 .在任何情况下,您都会在调度命令之前检查联系人的有效性 .
这是一个商业规则 . 应该问一个问题,这个商业规则对我的申请意味着什么?谁应该对实施这条规则负责,还是可以分担责任?
一种可能性是,是的,业务规则是关于发票的,因此发票服务应该负责实施它 .
但是,业务规则实际上是关于发票的创建 . 而且,您的架构中创建发票的所有者奇怪地不是发票服务 . 原因是命令的名称是
CreateInvoiceCommand
.让我们考虑一下 - 发票服务永远不会自己创建发票 . 它只是提供了这种能力 . 在此体系结构中,发票创建的实际所有者是命令的发件人 .
使用这种推理,如果业务规则是说无法针对无效收件人创建发票,则命令发送方有责任确保实现此业务规则 .
如果发票服务订阅了事件,而不是接收命令,那将是一种非常不同的情况 . 例如,一个名为
WidgetSold
的事件 . 在这种情况下,发票创建的所有者显然是发票服务,因此业务规则将在那里实施 .对,那是正确的 . 用户的意图是创建发票 . 因此,应该在此时强制执行发票创建的业务规则 . 如何发生这种情况(或者这种情况是否发生)是一个不同的问题 .
也正确 . 正如您所说,这种方法存在副作用,其中之一就是您最终可能会在系统中出现不一致的数据 . 这是SOA的现实之一 .
我不知道我是否同意 . 问这是一种有效的ISO货币吗?不同的是要求实体42根据另一个系统有效吗?我想是的 .
我同意,实际上,您可以在服务中实现此验证 . 我只是说我认为它不适合它 . 如果您想这样做,则必须先调出另一项服务或在本地存储所有联系人,因为您最初构建了问题 . 我认为在服务之外做这件事更简单 .