【问题标题】:How to model bank transfer in CQRS如何在 CQRS 中模拟银行转账
【发布时间】:2014-06-20 14:56:06
【问题描述】:

我正在阅读Accounting Pattern 并对在 CQRS 中实现它感到非常好奇。

我认为 AccountingTransaction 是一个聚合根,因为它保护不变量:

没有资金外泄,应该从一个账户转移到另一个账户。

public class AccountingTransaction {
    private String sequence;
    private AccountId from;
    private AccountId to;
    private MonetaryAmount quantity;
    private DateTime whenCharged;

    public AccountingTransaction(...) {
        raise(new AccountingEntryBookedEvent(sequence, from, quantity.negate(),...);
        raise(new AccountingEntryBookedEvent(sequence, to, quantity,...);
    }
}

AccountingTransaction 添加到其存储库时。它发布了几个AccountingEntryBookedEvent,用于在查询端更新相应账户的余额。

每个数据库事务更新一个聚合根,最终保持一致,到目前为止一切顺利。

但是,如果某些账户应用了转账限制,例如不能转账超过当前余额的数量,该怎么办?我可以使用查询端来获取帐户的余额,但我担心来自查询端的数据是陈旧的。

public class TransferApplication {
    public void transfer(...) {
        AccountReadModel from = accountQuery.findBy(fromId);
        AccountReadModel to = accountQuery.findBy(toId);
        if (from.balance() > quantity) {
            //create txn
        }
    }
}

我应该在命令端为帐户建模吗?我必须为每个数据库事务更新至少三个聚合根(来自/到帐户和帐户 txn)。

public class TransferApplication {
    public void transfer(...) {
        Account from = accountRepository.findBy(fromId);
        Account to = accountRepository.findBy(toId);
        Transaction txn = new Transaction(from, to, quantity);
        //unit or work locks and updates all three aggregates
    }
}

public class AccountingTransaction {
    public AccountingTransaction(...) {
        if (from.permit(quantity) {
            from.debit(quantity);
            to.credit(quantity);
            raise(new TransactionCreatedEvent(sequence, from, to, quantity,...);
        }   
    }
}

【问题讨论】:

  • 你想要使用 DDD 的银行应用程序吗?
  • @Singh 感谢您的评论。是的,带有 CQRS 的 DDD。
  • 我有一个关于 Bankapplication 的项目。你想要吗?
  • @Singh 我想看看,谢谢。如果您方便,可以通过电子邮件(hippoom@gmail.com)发送。
  • @Developer 你的答案在哪里? :(

标签: domain-driven-design cqrs event-sourcing


【解决方案1】:

修正了答案

最后我的解决方案是将事务作为域模型。

并将交易投射到 AccountBalance,但我实施了特殊的投射,以确保在发布实际事件之前每个数据都保持一致。

【讨论】:

    【解决方案2】:

    只有两个词:“Event Sourcing”和预留模式。 也许,但并非总是如此,您可能还需要“Sagas”模式。

    【讨论】:

      【解决方案3】:

      我记得这一点,但是与域事件相比,M Fowler 使用了不同的事件含义。他使用了“错误”术语,因为我们可以在他的“事件”定义中识别出一个命令。所以基本上他是在谈论命令,而领域事件是已经发生并且永远不会改变的事情。

      我可能没有完全理解 Fowler 所指的内容,但我会以不同的方式建模,更准确地说是尽可能接近域。我们不能简单地提取出一个始终可以应用于任何金融应用程序的模式,小细节可能会改变一个概念的含义。

      在 OP 的示例中,我想说我们可以进行非明确的“交易”:我们需要一个借记金额的帐户和另一个具有相同金额的贷方。我认为最简单的方法是通过 saga 来实现它。

      Debit_Account_A ->Account_A_Debited -> Credit_Account_B-> Account_B_Credited = 交易完成。

      这应该在最多几毫秒内发生,这足以更新读取模型。人类和浏览器的速度比几秒钟还要慢。并且用户知道按 F5 或等待几分钟/几小时。我不会太担心读取模型的准确性。

      如果事务是显式的,即域具有事务概念并且业务真正存储事务,则完全不同。但即使在这种情况下,交易也可能由多个帐户 ID 和一些金额以及一个已完成标志来定义。但是,此时继续没有意义,因为它真的取决于域的定义和用例。

      【讨论】:

        【解决方案4】:

        有些用例不允许最终的一致性。 CQRS 很好,但数据可能需要 100% 一致。 CQRS 并不暗示/要求最终的一致性。

        但是,事务/域模型存储将是一致的,并且余额将在 that 存储中保持一致,因为它代表当前状态。在这种情况下,事务无论如何都应该失败,而不管查询端是否不一致。这将是一种有点奇怪的用户体验,因此 100% 一致的方法可能会更好。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-01-30
          • 1970-01-01
          • 2010-09-13
          • 2019-07-22
          • 2014-08-27
          • 2017-03-21
          相关资源
          最近更新 更多