如何在CQRS中建立银行转账模型

时间:2014-05-04 06:25:18

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

我正在阅读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,用于更新查询端的相应帐户余额。

每个db事务更新一个聚合根,最终一致性,到目前为止一直很好。

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

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,...);
        }   
    }
}

4 个答案:

答案 0 :(得分:4)

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

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

答案 1 :(得分:3)

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

我可能并不完全理解福勒所指的是,但我会以不同的方式对事物进行建模,更准确地说是尽可能接近域。我们无法简单地提取可以始终应用于任何财务应用的模式,次要细节可能会改变概念的含义。

在OP的例子中,我说我们可以进行非明确的交易'我们需要一个帐户借记一笔金额和另一笔相同金额的信用卡。我认为,最简单的方法是通过传奇实现它。

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

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

如果交易是明确的,即域名具有交易概念,并且业务确实存储了完全不同的交易。但即使在这种情况下,交易也可能由多个账户ID和一些金额以及可能已完成的标志来定义。但是,在这一点上继续是没有意义的,因为真的取决于域的定义和用例。

答案 2 :(得分:0)

只需两个字:" Event Sourcing"使用预订模式。 也许,但并非总是如此,您可能需要" Sagas"模式也。

答案 3 :(得分:0)

固定答案

最后,我的解决方案是将“交易”作为域模型。

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