关于不变式,我们应该信任存储库吗? [英] Should we trust the repository when it comes to invariants?

查看:61
本文介绍了关于不变式,我们应该信任存储库吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我正在构建的应用程序中,有很多方案需要选择一组聚合来对其执行特定操作.例如,如果一堆 Reminder 聚合符合过期策略(只有一个),则可能不得不将它们标记为已过期.

In the application I'm building there are a lot of scenarios where I need to select a group of aggregates on which to perform a specific operation. For instance, I may have to mark a bunch of Reminder aggregates as expired if they meet the expiration policy (there is only one).

我有一个 ReminderExpirationPolicy 域服务,该域服务始终在传递提醒之前应用.该政策的作用类似于:

I have a ReminderExpirationPolicy domain service that is always applied before delivering reminders. This policy does something like:

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

到期策略当前已复制,因为它作为SQL谓词存在于 SqlReminderRepository.findRemindersToExpire 方法以及 Reminder.expire 聚合方法中.

The expiration policy is currently duplicated as it exists as a SQL predicate within the SqlReminderRepository.findRemindersToExpire method and also within the Reminder.expire aggregate's method.

这个问题的答案可能会强烈反对(尽管肯定有优缺点-也许是被广泛采用的做法),但我应该相信 Reminder.expire 方法只会在 ReminderExpirationPolicy 流程中被调用,并相信存储库实现将返回正确的提醒集到期,还是应该在 Reminder 聚合自身中保护不变式?

The answer to the question may be strongly opiniated (although there should definitely be pros and cons - and perhaps a widely adopted practice), but should I simply trust that the Reminder.expire method will only get called as part of the ReminderExpirationPolicy process and trust that the repository implementation will return the correct set of reminders to expire or should I also protect the invariant within the Reminder aggregate itself?

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

NOTE: I am aware that modifying multiple aggregates in a single transaction is sub-optimal and hinders scalability, but it's the most pragmatic solution in my case.

推荐答案

我应该简单地相信Reminder.expire方法将仅作为ReminderExpirationPolicy流程的一部分被调用,并相信存储库实现将返回正确的提醒集以到期,或者我也应该保护Reminder聚合自身内的不变量?

should I simply trust that the Reminder.expire method will only get called as part of the ReminderExpirationPolicy process and trust that the repository implementation will return the correct set of reminders to expire or should I also protect the invariant within the Reminder aggregate itself?

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

Short answer: you are backwards. You must protect the invariant within the Reminder aggregate; using the policy as a query specification is optional.

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

The key thing to realize is that, in your scenario, using the policy as a query specification is really is optional. Eliding persistence concerns, you should be able to do this

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

如果聚合拒绝更改状态,则会违反业务不变性.

with the aggregate declining to change state when doing so would violate the business invariant.

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

In general, the reason that this distinction is important is that any data that you could get by querying the repository is stale -- there could be another thread running coincident with yours that updates the aggregate after your query has run but before your expire command runs, and if that coincident work were to change the aggregate in a way that the policy would no longer be satisfied, then your expire command would come along later and threaten to violate the invariant.

由于聚合无论如何都必须保护自己免受这种竞争状况的影响,因此检查查询中的策略是可选的.

Since the aggregate has to protect itself against this sort of race condition anyway, checking the policy in the query is optional.

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

It's still a good idea, of course -- in the course of normal operations, you shouldn't be sending commands that you expect to fail.

如果稍微斜视一下,实际上发生的是expire命令和查询使用相同的策略,但是在命令执行路径正在评估writeModel状态是否满足该策略的情况下,查询正在评估是否readModel状态满足该策略.因此,这并不是真正的重复逻辑-我们在每种情况下都使用不同的参数.

What's really happening, if you squint a little bit, is that the expire command and the query are using the same policy, but where the command execution path is evaluating whether the writeModel state satisfies the policy, the query is evaluating whether the readModel state satisfies the policy. So it isn't really duplicated logic - we're using different arguments in each case.

但是,我的假设与您的假设有所不同(假设乐观锁定),即使在加载聚合后数据已过时并且我没有在聚合内执行失效规则,由于并发冲突,该操作仍将失败.

However, where my assumptions are different than yours is that from as far as I can see (assuming optimistic locking), even if the data become stale after aggregates are loaded and that I do not enforce the expiration rule within the aggregate, the operation would still fail because of a concurrency conflict.

是的,如果您确定处理命令的聚合版本与用于测试该策略的版本相同,那么并发写入将为您提供保护.

Yes, if you have assurance that the version of the aggregate that processes the command is the same as the version that was used to test the policy, then the concurrent write will protect you.

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

An additional consideration is that you are losing one of the benefits of encapsulation. If the policy check happens in the aggregate, then you are guaranteed that every code path which can expire the aggregate must also evaluate the policy. You don't get that guarantee if the aggregate is relying on the caller to check the policy (a variant on the "anemic domain" model).

这篇关于关于不变式,我们应该信任存储库吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆