Domain driven design 在响应域事件时加载聚合

Domain driven design 在响应域事件时加载聚合,domain-driven-design,event-sourcing,aggregateroot,domain-events,Domain Driven Design,Event Sourcing,Aggregateroot,Domain Events,我正在使用领域驱动设计和事件源实现一个应用程序。我正在SQL Server的DomainEvents表中存储所有域事件 我有以下汇总: - City + Id + Enable() + Disable() - Company + Id + CityId + Enable() + Disable() - Employee + Id + CompnayId + Enable() + Disable() 每一个都封装了自己的域逻辑

我正在使用领域驱动设计和事件源实现一个应用程序。我正在SQL Server的DomainEvents表中存储所有域事件

我有以下汇总:

- City
   + Id
   + Enable()
   + Disable()

- Company
   + Id
   + CityId
   + Enable()
   + Disable()

- Employee
   + Id
   + CompnayId
   + Enable()
   + Disable()
每一个都封装了自己的域逻辑和不变量。我将它们设计为单独的集合,因为一个城市可能有数千家(可能更多)公司,而公司也可能有大量员工。如果这些实体属于同一个聚合,我必须将它们加载在一起,在大多数情况下这是不必要的

调用Enable或Disable将生成域事件(例如,
CityEnabled
CompanyDisabled
EmployeeEnabled
)。这些事件包含启用或禁用实体的主键

现在我的问题是一个新的要求,如果一个城市启用/禁用,我必须启用/禁用所有相关公司。如果公司已启用/禁用,则员工也需要这样做

在我的事件处理程序中,如果发生了例如
CityDisabled
,将调用该事件处理程序 我需要为属于该城市的每个公司执行
禁用公司命令

但我怎么知道哪些公司应该受到这种变化的影响呢

我的想法:

  • 无法查询事件存储,因为我无法使用“where CityId=event.CityId”这样的条件

  • 让父对象知道其子ID,并在父对象生成的每个事件中放置所有子ID。这也是一个坏主意,因为事件创建者不应该关心谁将在以后消费这些事件。因此,事件中只应包含属于正在发生事件的信息

  • 为每个公司执行禁用公司命令。只有拥有匹配的
    CityId
    的公司才会更改其状态。即使我异步地这样做,它也会产生巨大的开销,将每个公司加载到这些事件上。而且,对于每个被禁用的公司,都应该重复相同的过程来禁用所有用户

  • 创建将parentId映射到childID的读取模型,并根据事件中的parentId加载childID。这听起来是最合适的解决方案,但问题是,当我禁用现有公司时,我如何知道是否创建了新公司

  • 我对上述任何解决方案都不满意。基本上,问题在于确定已发生事件的受影响总量


    也许您有更好的解决方案?

    您正在描述的问题可以通过收听
    CityDisabled
    事件的解决方案来解决。然后,它在该
    城市
    中找到所有
    公司
    公司ID
    (通过使用现有的
    读取模型
    或通过维护
    城市ID
    x
    公司ID
    的私有状态),并向每个公司发送
    禁用公司
    命令

    这同样适用于
    CompanyDisabled
    事件,关于禁用
    Employee


    对不起,一个城市/公司/雇员对我来说似乎很难,这些似乎不是一般的普遍语言的术语,它不是很DDD,但是我认为你的设计在这个问题上是正确的。

    你的要求是指在禁用城市时必须解雇一个公司禁用的事件吗? 如果不是,你的要求是一个被禁用的城市意味着所有的公司都被禁用,那么你要做的是在你的城市阅读模型投影上,你要收听城市被禁用的事件,并在阅读模型中标记被禁用的公司。(如果你的要求是为每个城市举办一场活动,那么康斯坦丁的回答是好的)

    你的模型更像是一种儿童/父母关系——这打破了传统的“蓝皮书”思维,但我建议在你的领域中用更多的城市ID来表现这种关系

    在我的应用程序中,类似这样的内容将被编码为

    public Task Handle(DoSomething command, IHandlerContext ctx) 
    {
       var city = ctx.For<City>().Get(command.CityId);
       var company = city.For<Company>().Get(command.CompanyId);
    
       company.DoSomething();
    }
    
    public Company : Entity<City>
    {
    
       public void DoSomething()
       {
            // Parent is the City
            if(Parent.Disabled)
                throw new BusinessException("City is disabled");
    
            Apply<SomethingDone>(x => {
                x.CityId = Parent.Id;
                x.CompanyId = Id;
                ...
            });
       }
    
    }
    
    公共任务句柄(DoSomething命令,IHandlerContext ctx)
    {
    var city=ctx.For().Get(command.CityId);
    var company=city.For().Get(command.CompanyId);
    公司。DoSomething();
    }
    上市公司:实体
    {
    公共无效剂量测定法()
    {
    //父母就是城市
    如果(父级禁用)
    抛出新的BusinessException(“城市已禁用”);
    应用(x=>{
    x、 CityId=Parent.Id;
    x、 CompanyId=Id;
    ...
    });
    }
    }
    

    (Psuedo代码是NServiceBus样式的代码,使用my library Aggregates.NET)

    很可能您根本不必在域(写入)端明确强制执行“启用/禁用所有相关公司,如果启用/禁用城市”之类的规则

    如果是这样的话,当一个城市被禁用时,没有必要在域内禁用所有公司。正如Charles在回答中提到的,只需引入一条规则,例如,“如果一家公司本身(直接)或其所在城市被禁用,则该公司被禁用”。公司及其员工也是如此

    这条规则应该在读取端实现。read模型中的公司将有两个属性:第一个是启用的,直接从域映射;第二个是EnabledEffective,可根据公司的启用值及其所在城市的启用值进行计算。当CityDisabled事件发生时,读取模型的事件处理程序将遍历读取模型中城市的所有公司,并将其EnabledEffective属性设置为false;当CityEnabled事件发生时,处理程序将城市的每家公司的EnableDefEffective属性设置回其自己的Enabled值。它是您将在UI中使用的EnableDefEffective属性

    对于CompanyEnabled/CompanyDisabled事件处理(对于员工而言),逻辑可能会稍微复杂一些,因为您必须同时考虑主办城市的事件信息和启用/禁用状态

    如果公司处于启用/禁用(生效)状态