在涉及不变量时,我们应该信任存储库吗?

时间:2016-10-27 15:35:21

标签: domain-driven-design batch-processing

在我构建的应用程序中,有很多场景我需要选择一组聚合来执行特定操作。例如,如果满足过期策略(只有一个),我可能必须将一堆Reminder聚合标记为已过期。

我有一个ReminderExpirationPolicy域名服务,在提交提醒之前始终会应用该服务。此政策的确如下:

reminderRepository.findRemindersToExpire().forEach(function (reminder) {
    reminder.expire(clockService.currentDateTime());
});

过期策略当前是重复的,因为它作为SqlReminderRepository.findRemindersToExpire方法中的SQL谓词以及Reminder.expire聚合方法中的SQL谓词存在。

这个问题的答案可能会得到强烈的反馈(尽管肯定有利有弊 - 也许是广泛采用的做法),但我应该只相信Reminder.expire方法只会被作为ReminderExpirationPolicy进程并相信存储库实现将返回正确的提醒集,或者我是否还要保护Reminder聚合本身内的不变量?

注意:我知道在单个事务中修改多个聚合是次优的并且阻碍了可伸缩性,但在我的情况下它是最实用的解决方案。

1 个答案:

答案 0 :(得分:3)

  

我应该只是相信Reminder.expire方法只会作为ReminderExpirationPolicy流程的一部分被调用,并相信存储库实现将返回正确的提醒集,或者我还应该保护Reminder聚合本身内的不变量?

简短回答:你倒退了。您必须保护提醒聚合中的不变量;使用策略作为查询规范是可选的。

要实现的关键是,在您的方案中,将策略用作查询规范实际上是可选的。消除持久性问题,你应该能够做到这一点

repo.getAll () { a -> a.expire(policy) ; }

当聚合拒绝改变状态时,会违反业务不变量。

通常,这种区别很重要的原因是,通过查询存储库可以获得的任何数据都是陈旧的 - 可能会有另一个运行重合的线程与您的一起在查询运行之后但在expire命令运行之前更新聚合,如果同步工作是以不再满足策略的方式更改聚合,那么您的expire命令将在稍后出现并威胁违反不变量。

由于聚合必须保护自己不受这种竞争条件的影响,因此检查查询中的策略是可选的。

当然,这仍然是一个好主意 - 在正常操作过程中,你不应该发送你期望失败的命令。

真正发生的事情,如果你稍微眯一眼,就是expire命令和查询使用相同的策略,但是命令执行路径正在评估writeModel状态是否满足策略,查询正在评估readModel状态是否满足策略。因此,它并不是真正重复的逻辑 - 我们在每种情况下都使用不同的参数。

  

但是,我的假设与您的假设不同的是,从我可以看到(假设乐观锁定),即使数据在加载聚合后变得陈旧并且我没有在聚合中强制执行到期规则,由于并发冲突,操作仍然会失败。

是的,如果您确信处理命令的聚合版本与用于测试策略的版本相同,则并发写入将保护您。

另一个考虑因素是您正在失去封装的一个好处。如果策略检查在聚合中发生,那么可以保证每个可以使聚合到期的代码路径也必须评估策略。如果聚合依赖于调用者来检查策略(这是"贫血域"模型的变体),则您无法获得该保证。