问题描述
假设员工可以使用雇主提供的某种“福利积分”购买产品。
我有一个 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/演员模式拟人化表示歉意)并且购买处于四种状态:开放、最终确定、已资助、过期和完成.
-
一旦购买实体知道要购买的物品及其成本,它就可以被最终确定,这将使其进入最终状态并防止未来更新物品和成本。它以这种状态保存。
-
然后它会尝试从员工的帐户中保留积分,并在截止日期前完成提款或保留消失。如果成功,它会转到“已资助”状态并保存。
对于获得资助并影响实际提款的购买,我可能会进行一些其他流程扫描(因为我们不是在这里进行事件溯源),这允许履行组件提取购买,并保存在“已完成”状态下购买(如果购买应该过期,有很多选择。