如何在 DDD 中管理域逻辑和事件之间的事务?
我正在研究 DDD 和事件源中的编程.
我看到一个示例,当调用域逻辑时(例如 Order.placeOrder()
),它会发布一个事件(例如 OrderPlaced
).并且该事件将作为事件存储发送到 MQ.
领域逻辑(Order.placeOrder()
)应该是一个原子API,如果使用Spring作为事务管理器,它应该有@Transactional
注解.p>
现在我的问题是:
如何确保数据库更改和事件发送在同一个事务中?即,如果在将数据提交到 DB 时出现任何错误,则该事件不应该发送到 MQ.
我知道有像 XA 或 2 阶段提交这样的解决方案来强制数据库更新和在同一事务中发送 MQ 消息.但现在似乎没有被广泛使用.
如果还是使用Spring的
@Transactional
注解,没有XA,是不是我们可以在事务提交成功后做一些逻辑呢?这样做的最佳做法是什么?
以下两个属性必须具备才能拥有一个可靠的系统:
- P1:已发布的域事件必须描述真正发生的变化(即确保没有鬼事件开始四处飞舞).
- P2:对触发域事件的数据库进行更改必须导致事件被发布(即不会丢失事件).
有以下几种可能来实现这一点,所有这些都是我自己使用过的,或者在项目中看到过的:
使用与您的应用程序使用相同数据库的消息传递基础架构,以便可以使用单个事务.当一个非常简单的消息传递基础架构就足够了,并且团队决定自己构建它时,此解决方案是可行的.
使用 2 阶段提交.我没有这个不再使用的印象,但可能它很少被谈论,因为它不是花哨的技术......
使用一些巧妙的技巧来确保这两个条件都成立.例如.用我所说的鸡和蛋的解决方案:
- 始终先同步发布事件,然后持久化到数据库.这确保了 P2 成立.
- 然后使用事件处理器检查事件流并检查是否可以在数据库中找到事件.如果没有,请从流中删除该事件.这可确保 P1 成立.
解决方案 3 需要仔细设计和审查系统每个部分在故障行为方面所做的保证,因此它可能是最难做到的.但它也是一个非常优雅的解决方案,一旦它起作用.
顺便说一句,我不同意应将 Spring 注释添加到域对象中,而是应添加到相应的应用程序服务中.这只是一个旁注.
I am studying on the programming in DDD and event source.
I saw one example that when a domain logic was called (e.g. Order.placeOrder()
) it would publish an event (e.g. OrderPlaced
). And the event would be sent to MQ as the event store.
The domain logic (Order.placeOrder()
) should be an atomic API, and it should have @Transactional
annotation if using Spring for the transaction manager.
And now my question is:
How to make sure the DB change and event sending are within the same transaction? i.e. If there any error when committing data into DB, the event should never send to MQ.
I know that there is solution like XA or 2 phase commit to force the DB update and sending MQ messages within the same transaction. But seems it is not widely used nowadays.
If still using Spring
@Transactional
annotation and no XA, is it possible that we do some logic after the transaction is committed successfully? What is the best practice to do that?
解决方案
The following two properties must hold to have a reliable system:
- P1: Published domain events MUST describe a change that really happened (i.e. make sure no ghost events start flying around).
- P2: Changes to the DB that trigger domain events MUST result in an event being published (i.e. don't lose events).
There are the following possibilities to achieve this, all of which I've either used myself or seen being used in a project:
Use a messaging infrastructure that uses the same database as your application, so that a single transaction can be used. This solution is viable when a very simple messaging infrastructure suffices, and the team decides to build it themselves.
Use 2 phase commits. I don't have the impression that this is not used anymore, but maybe it's less talked about, because it isn't fancy technology...
Use some clever trickery to ensure both conditions hold. E.g. with what I call the chicken and egg solution:
- Always publish events synchronously first, then persist to the DB. This ensures P2 holds.
- Then use an event processor that inspects the event stream and checks whether an event can be found in the DB. If not, remove the event from the stream. This ensures P1 holds.
Solution 3 requires careful design and review of the guarantees each part of the system makes in terms of failure behavior, so it is probably the most difficult one to get right. But it is also a very elegant solution, once it works.
By the way, I don't agree that Spring annotations should be added to domain objects, but rather to the respective app services. This only as a side note.
相关文章