我正在阅读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,...);
}
}
}
答案 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,但是我实施了特殊的投影,以确保在发布实际事件之前每个数据都保持一致。