总账的聚合设计
Aggregate Design for Ledger
我正在尝试使用 DDD 设计一个复式记账分类账,运行 在定义聚合根时遇到了一些麻烦。共有三个域模型:
LedgerLine
:包含数量、创建时间戳等数据的单个订单项
LedgerEntry
:记入账本。每个条目包含多个 LedgerLine
,其中借方和贷方行必须平衡。
LedgerAccount
: 账本中的账户。有两种类型的账户:(1) 内部账户(例如现金)(2) 外部账户(例如关联的银行账户)。外部帐户可以是 added/removed.
在网上阅读了一些文章后(例如这篇:https://lorenzo-dee.blogspot.com/2013/06/domain-driven-design-accounting-domain.html?m=0)。看起来 LedgerEntry
应该是一个聚合根,包含对 LedgerLine
的引用。 LedgerAccount
应该是另一个聚合根。 LedgerLine
s 将保存相应的 LedgerAccount
的 ID。
虽然这很有意义,但我无法弄清楚在添加分类帐行时如何更新分类帐帐户的余额。上面的文章建议应该动态计算余额,这意味着添加 LedgerEntry
s 时不需要更新它。但是,我将 Amazon QLDB 用于分类帐,他们的解决方案工程师特别建议应计算余额并将其存储在 LedgerAccount
上,因为 QLDB 并未针对此类“扫描大量文档”操作进行优化.
现在问题来了:
- 如果我在添加
LedgerEntry
时同步更新 balance
字段,那么我将在一次操作中更新两个聚合,这违反了一致性边界。
- 如果我在收到“添加
LedgerEntry
”操作发出的事件后异步更新 balance
字段,那么如果我添加另一个 LedgerEntry
花费账户余额,这可能导致透支。
- 如果我将
LedgerAccount
模型归入 LedgerEntry
的同一集合中,那么我将失去 add/remove 个人 LedgerAccount
的能力,因为我无法查询它们直接。
- 如果我去掉
balance
字段并即时计算它,那么可能会出现性能问题,因为 (1) QLDB 限制 (2) 分类账行数是无限的。
那么这里的正确设计是什么?非常感谢任何帮助!
您可以使用 Saga 模式来确保整个过程完成或失败。
这是一本入门书...https://medium.com/@lfgcampos/saga-pattern-8394e29bbb85
- 我会将 'reserved funds' 拥有的 collection 添加到分类帐帐户。
- 分类帐将有 'Actual' 余额和 'Available Balance'。
- 'Available Balance' 是 'Actual' 余额减去 'reserved funds'
的值
使用 Saga 管理流程:
尝试在帐户总计上保留资金。 Ledger Account 将检查其可用余额(实际减去保留资金总额),如果足够,则将另一笔保留资金添加到其 collection。如果预订成功,帐户聚合将 return 一个预订唯一 ID。如果预订失败,则无法发布条目。
尝试完成复式记账。如果失败,请向引用预订唯一 ID 的帐户聚合发送 'release reservation' 命令,这将删除预订,我们回到开始的地方。
复式记账完成后,向'complete' reservation Account 发送命令,其中包含reservation unique id。然后,帐户聚合将删除预留并调整其实际余额。
通过这种方式,您可以管理分布式事务,而不会透支账户。
聚合根应该作为事务边界。多方交易跨越多个账户,因此一个账户不能。
所以账本本身就是一个聚合根。会计事务应对应数据库事务。
实际上,“账本本身”并不意味着单例。可以是组织分部*时间段分类账。它通常在非计算机事件源系统中。
更新。
分类帐帐户余额只是分类帐的一个视图。作为一种观点,它具有某个已知事件的状态。在决定是否接受操作时,您应该确保分类帐的实际状态是截至余额的最新处理状态。如果不是 - 应先处理较新的事件,然后再尝试帐户操作。
我正在尝试使用 DDD 设计一个复式记账分类账,运行 在定义聚合根时遇到了一些麻烦。共有三个域模型:
LedgerLine
:包含数量、创建时间戳等数据的单个订单项LedgerEntry
:记入账本。每个条目包含多个LedgerLine
,其中借方和贷方行必须平衡。LedgerAccount
: 账本中的账户。有两种类型的账户:(1) 内部账户(例如现金)(2) 外部账户(例如关联的银行账户)。外部帐户可以是 added/removed.
在网上阅读了一些文章后(例如这篇:https://lorenzo-dee.blogspot.com/2013/06/domain-driven-design-accounting-domain.html?m=0)。看起来 LedgerEntry
应该是一个聚合根,包含对 LedgerLine
的引用。 LedgerAccount
应该是另一个聚合根。 LedgerLine
s 将保存相应的 LedgerAccount
的 ID。
虽然这很有意义,但我无法弄清楚在添加分类帐行时如何更新分类帐帐户的余额。上面的文章建议应该动态计算余额,这意味着添加 LedgerEntry
s 时不需要更新它。但是,我将 Amazon QLDB 用于分类帐,他们的解决方案工程师特别建议应计算余额并将其存储在 LedgerAccount
上,因为 QLDB 并未针对此类“扫描大量文档”操作进行优化.
现在问题来了:
- 如果我在添加
LedgerEntry
时同步更新balance
字段,那么我将在一次操作中更新两个聚合,这违反了一致性边界。 - 如果我在收到“添加
LedgerEntry
”操作发出的事件后异步更新balance
字段,那么如果我添加另一个LedgerEntry
花费账户余额,这可能导致透支。 - 如果我将
LedgerAccount
模型归入LedgerEntry
的同一集合中,那么我将失去 add/remove 个人LedgerAccount
的能力,因为我无法查询它们直接。 - 如果我去掉
balance
字段并即时计算它,那么可能会出现性能问题,因为 (1) QLDB 限制 (2) 分类账行数是无限的。
那么这里的正确设计是什么?非常感谢任何帮助!
您可以使用 Saga 模式来确保整个过程完成或失败。
这是一本入门书...https://medium.com/@lfgcampos/saga-pattern-8394e29bbb85
- 我会将 'reserved funds' 拥有的 collection 添加到分类帐帐户。
- 分类帐将有 'Actual' 余额和 'Available Balance'。
- 'Available Balance' 是 'Actual' 余额减去 'reserved funds' 的值
使用 Saga 管理流程:
尝试在帐户总计上保留资金。 Ledger Account 将检查其可用余额(实际减去保留资金总额),如果足够,则将另一笔保留资金添加到其 collection。如果预订成功,帐户聚合将 return 一个预订唯一 ID。如果预订失败,则无法发布条目。
尝试完成复式记账。如果失败,请向引用预订唯一 ID 的帐户聚合发送 'release reservation' 命令,这将删除预订,我们回到开始的地方。
复式记账完成后,向'complete' reservation Account 发送命令,其中包含reservation unique id。然后,帐户聚合将删除预留并调整其实际余额。
通过这种方式,您可以管理分布式事务,而不会透支账户。
聚合根应该作为事务边界。多方交易跨越多个账户,因此一个账户不能。
所以账本本身就是一个聚合根。会计事务应对应数据库事务。
实际上,“账本本身”并不意味着单例。可以是组织分部*时间段分类账。它通常在非计算机事件源系统中。
更新。
分类帐帐户余额只是分类帐的一个视图。作为一种观点,它具有某个已知事件的状态。在决定是否接受操作时,您应该确保分类帐的实际状态是截至余额的最新处理状态。如果不是 - 应先处理较新的事件,然后再尝试帐户操作。