如何在CQRS中建模银行转账

18

我正在阅读《会计模式》,并对在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
        }
    }
}

我应该在命令端对账户进行建模吗?每次数据库事务(从/到帐户和帐户交易)中至少要更新三个聚合根。

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

@Singh,感谢您的评论。是的,DDD与CQRS。 - Yugang Zhou
我有一个关于银行应用程序的项目。你想要吗? - Kalu Singh Rao
我提供能够帮助其他开发者的答案。所以请检查答案。 - Kalu Singh Rao
有一个关于银行转账和最终一致性的很棒的视频,但我找不到链接。 :D - inf3rno
1
@开发者 你的回答在哪里?:( - kitta
显示剩余2条评论
4个回答

4

有一些使用情况不允许最终一致性。CQRS是可以的,但数据可能需要100%的一致性。CQRS并不意味着/要求最终一致性。

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


4

我记得一些细节,但是M Fowler对于"event"的定义不同于领域事件。他使用了错误的术语,因为我们可以在他的“事件”定义中识别出命令。所以基本上他谈论的是命令,而领域事件是已经发生的事情,它永远不会改变。

我可能没有完全理解Fowler所指的内容,但是我会更加精确地模拟,尽量接近领域的定义。我们不能简单地提取一个可以应用于任何金融应用程序的模式,细节上的小变化可能会改变概念的意义。

在OP的例子中,我认为我们可以有非显式的“交易”:我们需要将一个帐户借贷一定金额,然后另一个帐户贷入相同金额。最简单的方法是通过实现Saga来完成。

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

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

如果交易是显式的,即领域有一个交易概念并且业务确实存储交易,那就完全不同了。但是即使在这种情况下,交易可能也会由一些帐户ID和一些金额以及可能的完成标志来定义。然而,在这一点上继续讨论是没有意义的,因为它真的取决于领域的定义和用例。


1

修正答案

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

并将交易投影到账户余额,但我实现了特殊的投影,以确保在发布实际事件之前每个数据的一致性。


0
只需两个词:“事件溯源”与预定模式。 而且可能,但不总是,您也需要使用“Sagas”模式。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接