DDD:如何防止购买在没有相关提款的情况下被持久化

问题描述

假设员工可以使用雇主提供的某种“福利积分”购买产品。

我有一个 CreditsAccount 实体,用于保存积分数量和对员工的引用,我想将 Purchase 建模为保存对 CreditsAccount 的引用的实体,并且产品是采购。

如果我有一个 PurchaseRepository.save() 方法,如何防止 Purchase 被保存而不从 CreditsAccount 中撤出?

解决方案 1

购买是聚合 CreditsAccount 的子实体,我没有任何 PurchaseRepository 而是帐户上的一个方法,例如 CreditsAccount.AddPurchase(purchase) 可以在内部进行提款。当我使用 CreditsAccountRepository.save(account) 保留 CreditsAccount 时,我也会将购买保留在正确的表中。

但这意味着每次我加载 CreditsAccount 进行新购买时,我还必须加载该帐户上的购买列表,对吗?

解决方案 2

我构建了一个新的 PurchaseTransaction 实体,其中包含对 CreditsAccount 和 Product 的引用,并提供了一个类似于 PurchaseTransaction.commit()方法。 在提交时,它实际上根据产品价格从引用的帐户中取款,并且其内部状态从 uncommitted 切换到 committed。 然后我有一个 PurchaseTransactionRepo.save(purchaseTransaction) :

  • 如果事务未提交,则什么都不做或引发错误
  • 如果交易已提交,则使用新金额保留 CreditsAccount,并在适当的表上创建购买。

在这种情况下,PurchaseTransactionRepo 仅提供一种写入数据库方法,因为实际上 PurchaseTransaction 并不真正属于任何表,而 PurchaseRepository 是只读的并且仅提供获取“已提交”购买的方法

我不喜欢这个解决方案的是有一个回购,它实际上对 PurchaseTransaction 的状态(提交与否)进行了某种验证逻辑,而且似乎误导了未提交的 PurchaseTransaction 并没有真正持久化.

你能给我推荐其他符合 DDD 概念的解决方案吗?

一些上下文: 不是一个真正的应用程序,而是一个 kata 练习,我必须从一个贫乏的领域重构到一个丰富的领域。在贫血的情况下,有一项服务可以在一次交易中提取帐户并保存购买。然后我可以通过 api 调用获取购买清单。我想用丰富的实体对其进行建模并“强制”无效状态不可“持久化”

解决方法

Purchase 似乎是 saga 的一个很好的候选者,可以将其视为代表进程的实体。在没有更多背景的情况下,我会将其建模为类似于现实世界中付款通常处理的方式(对非常 CQRS/演员模式拟人化表示歉意)并且购买处于四种状态:开放、最终确定、已资助、过期和完成.

  • 一旦购买实体知道要购买的物品及其成本,它就可以被最终确定,这将使其进入最终状态并防止未来更新物品和成本。它以这种状态保存。

  • 然后它会尝试从员工的帐户中保留积分,并在截止日期前完成提款或保留消失。如果成功,它会转到“已资助”状态并保存。

对于获得资助并影响实际提款的购买,我可能会进行一些其他流程扫描(因为我们不是在这里进行事件溯源),这允许履行组件提取购买,并保存在“已完成”状态下购买(如果购买应该过期,有很多选择。