Domain driven design 当列表中存在不变量时定义聚合根

Domain driven design 当列表中存在不变量时定义聚合根,domain-driven-design,cqrs,event-sourcing,aggregateroot,Domain Driven Design,Cqrs,Event Sourcing,Aggregateroot,我正在做一个家庭日托应用程序,我想我应该试试DDD/CQRS/ES,但我在设计好这些应用程序时遇到了一些问题。该领域可以简单地描述为: 孩子入学了 一个孩子可以到达 孩子可以离开 目标是跟踪访问时间,生成发票,记录访问记录(如午餐内容、受伤等)。到目前为止,这些其他操作将是与系统最常见的交互,因为访问每天开始一次,但有趣的事情一直在发生 我正在努力解决的不变量是: 如果孩子已经到了,他们就不能来了 据我所知,我有以下几种选择 1。单个聚合根子 创建单个Childaggregate roo

我正在做一个家庭日托应用程序,我想我应该试试DDD/CQRS/ES,但我在设计好这些应用程序时遇到了一些问题。该领域可以简单地描述为:

  • 孩子入学了
  • 一个孩子可以到达
  • 孩子可以离开
目标是跟踪访问时间,生成发票,记录访问记录(如午餐内容、受伤等)。到目前为止,这些其他操作将是与系统最常见的交互,因为访问每天开始一次,但有趣的事情一直在发生

我正在努力解决的不变量是:

  • 如果孩子已经到了,他们就不能来了
据我所知,我有以下几种选择

1。单个聚合根

创建单个
Child
aggregate root,其中包含事件
childregistered
ChildArrived
ChildLeft

这似乎很简单,但由于我希望每个其他事件都与访问关联,这意味着访问将是
子项
聚合的一个实体,每次我想添加注释或任何内容时,我都必须为该子项寻找所有访问的来源。看起来效率很低,也很不相关——孩子本身,以及每一次拜访,都与孩子午餐吃什么无关

2。聚合
子项的根
访问

Child
将只获取
childregistered
,而
Visit
将获取
ChildArrived
ChildLeft
。在这种情况下,我不知道如何维护不变量,除了让
Visit
接受一项仅用于此目的的服务之外,我已经看到这是不鼓励的

有没有其他方法可以使用此设计强制执行不变量

3。这是一个假不变量

我想这是可能的,我应该防止多人同时登录同一个孩子,或者延迟意味着用户多次点击“登录”按钮。我认为这不是答案

4。我错过了一些明显的东西

这似乎是最有可能的-当然这不是一些特殊的雪花,这通常是如何处理的?我几乎找不到有多个AR的示例,更不用说有列表的示例了。

听起来像是你的不变量中的“如果孩子已经在这里,他们就无法到达”中的“这里”可能是一个聚合的想法。可能是
位置
日间护理中心
。从这里开始,确保
不能到达两次似乎很简单,除非他们以前离开过

当然,那么这个总量将是相当长寿的。然后您可以考虑一个集合用于<代码>营业日<代码>或类似于限制孩子到达和离开的原始计数。 只是个主意。不一定是解决这个问题的方法。

您正在大量谈论
访问
以及在
访问
期间发生的事情,因此它似乎是一个重要的领域概念。
我想你也会有一个
日托中心
,所有受照顾的
儿童都会参加

因此,我将使用这个聚合根:

  • 日托中心
  • 孩子
  • 拜访
顺便说一句:我看到另一个不变量:
“儿童不能同时在多个日托中心”

“多次点击“登录”按钮” 如果每个命令都有一个唯一的id,该id是为每个有意的尝试生成的,而不是由每次单击(无意的)生成的,则可以缓冲最后n个接收到的命令id并忽略重复项

或者,您的消息传递基础设施(服务总线)可以为您处理这些问题

创建访问 由于您使用多个聚合,因此必须查询一些(可靠、一致的)存储,以确定不变量是否满足要求。
(或者,如果冲突很少发生,并且手动“取消”无效的
访问是合理的,那么最终的一致读取模型也会起作用……)

由于
子项
只能有一次当前的
访问
,因此
子项
仅存储上次开始的
访问
的少量信息(事件)

无论何时开始新的
访问
,都会查询“真相来源”(写入模型)以查找任何先前的
访问
,并检查
访问
是否已结束

(另一种选择是,
访问
只能通过
子项
聚合来结束,在
子项
中再次存储一个“结束”-事件,但这对我来说感觉不太好……但这只是个人意见)

查询(验证)部分可以通过一个特殊的服务来完成,或者只需将一个存储库传递给该方法并直接在那里查询即可——这次我选择第二个选项

下面是一些C#ish brain编译的伪代码,以表达我认为您可以如何处理它:

公共类DayCareCenterId
{
公共字符串值{get;set;}
}
公营日托中心
{
公共DayCareCenter(DayCareCenterId,字符串名称)
{
RaiseEvent(创建的新DayCareCenter(id、名称));
}
私有无效应用(DayCareCenterCreated@event)
{
//...
}
}
公共类访问
{
公共字符串值{get;set;}
}
公开课堂参观
{
公众访问(访问id、儿童id、日期时间开始)
{
RaiseEvent(创建的新访问(id、childId、start));
}
私有无效应用(VisitCreated@event)
{
//...
}
公众访问()
{
RaiseEvent(新访问者(id));
}
私有无效应用(Visited@event)
{
//...
}
}
公营儿童
{
公共字符串值{get;
Greg Young:
    The big mental leap in this kind of system is to realize that 
    you are not the book of record. In the warehouse example the 
    *warehouse* is the book of record. The job of the computer 
    system is to produce exception reports and estimates of what 
    is in the warehouse