我的域不关心计数。
这相当于说您没有任何需要或管理计数的不变量。这意味着不存在计数有意义的聚合,因此计数不应该在您的域模型中。
按照您的建议将其作为 PostCreated 事件的计数来实现,或者通过对 Post 存储运行查询来实现,或者......任何适合您的方式。
如果除了我的视图之外我不在任何地方使用这些计数,我是否应该让我的查询处理程序负责持久化它们?
那个,或者读取模型中的其他任何东西——但是如果你的读取模型支持像select categoryId, count(*) from posts...这样的东西,你甚至不需要那么多。
域服务将永远希望对类别中的帖子进行计数
对于一个域服务来说,这是一件很奇怪的事情。域服务通常是无状态查询支持 - 通常它们被聚合用于在命令处理期间回答某些问题。它们自己实际上并不强制执行任何业务不变量,它们只是在这样做时支持聚合。
在两个级别上查询写入模型要使用的计数的读取模型没有意义。首先,读取模型中的数据是陈旧的——您从该查询中获得的任何答案都可能在您完成查询和尝试提交当前事务之间发生变化。其次,一旦您确定陈旧数据有用,就没有特别的理由更喜欢在事务期间观察到的陈旧数据而不是之前的陈旧数据。也就是说,如果数据无论如何都是陈旧的,您不妨将其作为命令参数传递给聚合,而不是将其隐藏在域服务中。
OTOH,如果您的域需要它——如果存在一些约束计数的业务不变量,或者使用该计数来约束其他事物的业务不变量——那么需要在一些聚合中捕获该不变量> 控制计数状态。
编辑
考虑两个同时运行的事务。在事务 A 中,聚合 id:1 运行需要对象计数的命令,但聚合不控制该计数。在事务 B 中,正在创建聚合 id:2,这会更改计数。
简单的例子,两个交易碰巧发生在连续的区块中
A: beginTransaction
A: aggregate(id:1).validate(repository.readCount())
A: repository.save(aggregate(id:1))
A: commit
// aggregate(id:1) is currently valid
B: beginTransaction
B: aggregate(id:2) = aggregate.new
B: repository.save(aggregate(id:2))
B: commit
// Is aggregate(id:1) still in a valid state?
我表示,如果 aggregate(id:1) 仍处于有效状态,则其有效性不依赖于 repository.readCount() 的及时性——使用事务开始之前的计数本来也一样好。
如果aggregate(id:1)不是有效状态,那么它的有效性依赖于它自己边界之外的数据,这意味着领域模型是错误的。
在更复杂的情况下,两个事务可以同时运行,这意味着我们可能会看到聚合(id:2)的保存发生在计数的读取和聚合(id:1)的保存之间,像这样
A: beginTransaction
A: aggregate(id:1).validate(repository.readCount())
// aggregate(id:1) is valid
B: beginTransaction
B: aggregate(id:2) = aggregate.new
B: repository.save(aggregate(id:2))
B: commit
A: repository.save(aggregate(id:1))
A: commit
考虑一下为什么有一个控制状态的聚合可以解决问题可能会很有用。让我们改变这个例子,这样我们就有一个包含两个实体的聚合......
A: beginTransaction
A: aggregate(version:0).entity(id:1).validate(aggregate(version:0).readCount())
// entity(id:1) is valid
B: beginTransaction
B: entity(id:2) = entity.new
B: aggregate(version:0).add(entity(id:2))
B: repository.save(aggregate(version:0))
B: commit
A: repository.save(aggregate(version:0))
A: commit
// throws VersionConflictException
编辑
提交(或保存,如果您愿意)可以抛出的概念是一个重要的概念。它强调模型是一个独立于记录系统的实体。在简单的情况下,模型防止无效写入,记录系统防止写入冲突。
务实的答案可能是让这种区别变得模糊。尝试对计数应用约束是 Set Validation 的一个示例。除非集合的表示位于聚合边界内,否则域模型将遇到问题。但是关系数据库往往擅长集合——如果你的记录系统恰好是一个关系存储,你可以通过使用数据库约束/触发器来维护集合的完整性。
您如何处理此类问题应基于对特定故障的业务影响的理解。缓解,而不是预防,可能更合适。