如何在 DDD 中处理外部有状态 Web 服务?

How to deal with external stateful webservices in DDD?

场景#1

场景 #2

场景 #3

场景 #4(问题开始...)

场景 #n [#4 的许多变体] ...

所以我无法就如何在 DDD 中更改状态时使用 Web 服务制定明确的标准。

理想情况下,我会在我的存储中进行更改,然后拥有某种集成层来复制外部数据库或服务中的这些更改,例如事件。但是,大多数情况下存在写后读一致性要求,如果您在操作 returns 后刷新,屏幕上会显示正确的数据(例如:来自同一网络服务的付款收据付款已完成)。

另一个例子:一个屏幕允许保存关于一本书的数据,但是书籍规格保存在数据库中,描述(多种语言)保存在外部服务中。操作必须一致,如果用户单击 "Save" 并刷新,屏幕需要一起显示,不能只显示带有旧翻译的规范或根本不显示翻译,因为它们正在被复制到另一个数据库。

哪个是可靠的决定标准?

答案是阅读Life Beyond Distributed Transactions

简而言之,尝试可靠地协调不同位置的写入非常昂贵。在大多数情况下,最好在您的设计中承认您不能同时出现在所有地方,并投资于处理该事实的后果。

我一直遵循的规则是 Aggregate 应该是纯粹的,不依赖于进行 IO 调用的服务(即磁盘或网络)。每次在给定状态下执行命令时,它都应该给出相同的结果。注入服务或将其作为方法调用中的参数传递违反了此规则。抽象的想法是聚合永远不应该根据它不拥有的数据做出决定。

然而,每个复杂系统包含的不仅仅是聚合体。它还包含 Sagas/Process 对业务流程建模的管理器(从现在开始只是 Saga)。要设计一个完美的 Saga,只需要一个清晰的业务流程和幂等的端点。

一个 Saga 启动,然后监听域中的变化,通常是通过订阅域事件。它通过向相应的端点发送命令来对它们做出反应。请注意,我使用术语端点来指代以幂等方式处理命令的任何接收器。纯粹的聚合就是这样的端点。但是 Saga Endpoint 也可以是外部系统,比如支付网关。这样的网关不应该使用现有的PaymentID(您的系统发送的不透明字段)发起新的支付。

结论:据我所知,你所有的场景都可以实现为Sagas。

您可以阅读更多关于 Sagas 的内容 here and here and here