Skip to content

事务(transaction)

事务机制可以让一组变更达到

  • Atomic 原子性,要么都执行,要不都不执行
  • Consistent 一致性,相同的数据的值是一致的
  • Isolate 隔离性,并发执行的变更序列相互隔离,不会导致错误
  • Durable 持久性,变更结果记录到持久化设备中不会丢失

即 ACID 特性。大部分的关系型数据库都提供了这样的能力。对于业务开发来说,事务非常重要。

DDD实现的时候什么场景下会需要事务?

DDD实现时,需要持久化的数据主要有:聚合状态、领域事件、发件箱消息、读模型等。只要涉及到这些数据的一致性的,都需要事务。

以下动作要满足ACID:

  • 一个聚合的一次变更
  • 一个命令的执行过程
  • 监听领域事件,完成一个动作

什么是事务编排?

一个动作要满足ACID不代表一个事务中只能有且仅有一个这个动作。比如,在一次事务中执行两个命令,这两个命令的执行都满足ACID,自然每个命令的执行也是满足ACID的。把哪些动作放到一个事务中,即划分事务边界,做事务编排。

在哪里去编排事务?

在领域模块之外。领域模块表达纯粹的业务逻辑。不同业务场景,可以会组合不同的命令,或者有不同的性能要求,那自然采用不同的事务编排策略。这些主要都是非业务性,更加技术倾向的,为了保持灵活性,所以把这些编排放到领域模块外。

事务的范围应该设多大?

符合业务逻辑,性能可以接受的情况下,大一些的事务更好。采用大一些的事务,让数据库帮你实现ACID,从而降低开发成本。如果事务过大,导致出现性能问题了,或者因为部分失败而导致整体回滚,不符合业务逻辑了,那自然就要把这个大的事务分拆得更小一些。事务范围更小,有需要一致性,那必定需要最终一致,导致更大的开发和维护负担。

我们使用的数据库不支持ACID,怎么办?

一个数据库如果完全不支持ACID,那么是不适合作为聚合的持久化数据库的,比如redis,不支持原子性、持久性。这种时候,必须选择一种可以ACID的数据库。一般这都不难。

如果只是部分支持,比如不支持多个操作的ACID,不支持不同表的操作的ACID等,可以尝试每次命令只修改一个聚合,聚合间只能通过领域事件达到最终一致性的方案。这种方式下,每次事务只执行一个命令,并且这个命令只能变更一个聚合,持久化的时候,把这个聚合的变更及聚合发出的事件一起写入到数据库中。比如MangoDB,适合这种方式。

如果可以的话,我们应该尽量选择ACID功能全面的数据库,减少对实现模型和建模的影响。

如何保证并发时正确性?

一个聚合对象不应该被并行的修改,如果发生了并发访问,应该加锁。要么加悲观锁,将变更串行化,要么采用乐观锁,让后执行者失败。如何加锁,具体问题具体分析。

一般情况下,在性能够用时,推荐采用数据库悲观锁,这种方式实现最简单,能保证正确性。