加载对域事件做出反应的聚合

时间:2017-10-20 11:30:09

标签: domain-driven-design event-sourcing aggregateroot event-store domain-events

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

我有以下聚合:

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

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

- Employee
   + Id
   + CompnayId
   + Enable()
   + Disable()

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

调用启用或禁用会产生域事件(例如CityEnabledCompanyDisabledEmployeeEnabled)。这些事件包含已启用或已禁用实体的主键。

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

在我的事件处理程序中,如果例如CityDisabled已发生,则调用该处理程序 我需要为属于该城市的每家公司执行DisableCompanyCommand

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

我的想法:

  1. 无法查询活动商店,因为我无法使用'其中CityId = event.CityId'

  2. 等条件
  3. 让父母知道其子ID并将所有子ID放在父级生成的每个事件中。这也是一个坏主意,因为事件创建者不应该关心谁将在以后使用这些事件。因此,只有属于发生事件的信息应该在事件中。

  4. 为每家公司执行DisableCompanyCommand。只有匹配CityId的公司才会改变其状态。即使我会以异步方式执行此操作,但这会对每个公司在这些事件上产生巨大的开销。并且对于每个被禁用的公司,应该重复相同的过程以禁用所有用户。

  5. 创建将ParentIds映射到ChildIds并根据事件中的parentId加载childIds的读取模型。这听起来像是最合适的解决方案,但问题是,在禁用现有公司时,如何创建新公司?

  6. 我对上述任何解决方案都不满意。基本上问题是确定发生事件的受影响聚合。

    也许你有更好的解决方案。

    ...问候

3 个答案:

答案 0 :(得分:2)

您所描述的内容可以由听取CityDisabled事件的Saga/Process manager来解决。然后,它会找到CompanyIds中所有Companies的{​​{1}}(使用现有的City或保持私有状态Read model x {{1} })并向每个人发送CityIds命令。

同样适用于CompanyIds事件,关于禁用DisableCompany

P.S。禁用城市/公司/员工对我来说似乎是CRUD,这些似乎不是来自普通无处不在的语言的术语,它不是非常DDD-ish但我认为你的设计是正确的问题

答案 1 :(得分:0)

您的要求是否意味着您必须在禁用城市时触发CompanyDisabled事件?

如果没有 - 并且您的要求只是残疾城市意味着所有公司都被禁用,那么您要做的就是在您的城市阅读模型投影中,您将收听CityDisabled事件并在您的阅读模型中标记禁用的公司。 (如果你的要求是为每个城市发射一个事件,那么康斯坦丁的回答是好的)

你的模特更像是一种孩子/父母的关系 - 它在传统的“蓝皮书”思想中有所突破,但我建议在你的领域中用超过CityId代表这种关系。

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

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;
            ...
        });
   }

}

(Psuedo代码是NServiceBus样式代码并使用我的库Aggregates.NET)

答案 2 :(得分:0)

如果在域(写入)方面启用/禁用城市',则很可能不必明确强制执行“启用/禁用所有相关公司”等规则。< / p>

如果是这样,则在域内禁用城市时无需禁用所有公司。正如查尔斯在他的回答中所提到的,只是引入一条规则,例如,“如果公司自身被禁用(直接)或其城市被禁用,则公司被禁用”。与公司及其员工一样。

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

使用CompanyEnabled / CompanyDisabled事件处理(对于Empoyees),逻辑可能会更复杂,因为您必须考虑主机城市的事件信息和启用/禁用状态。

如果域侧真的需要(有效)启用/禁用公司/员工状态(例如影响这些聚合处理命令的方式),请考虑从读取端获取 EnabledEffective 值并将其与命令对象一起传递。