2

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

我有以下聚合:

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

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

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

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

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

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

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

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

我的想法:

  1. 查询事件存储是不可能的,因为我不能使用像'where CityId = event.CityId'这样的条件

  2. 让父母知道它的孩子 id 并将所有孩子 id 放在父母产生的每个事件中。这也是一个坏主意,因为事件创建者不应该关心以后谁会使用这些事件。因此,只有属于正在发生的事件的信息才应该在事件中。

  3. 为每家公司执行DisableCompanyCommand。只有匹配的公司CityId才会改变他们的状态。即使我会异步执行此操作,它也会在每个公司的这些事件上产生巨大的开销。而且对于每家被禁用的公司,应该重复相同的程序来禁用所有用户。

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

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

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

4

3 回答 3

2

您所描述的内容可以由监听事件的Saga/Process 管理器解决。CityDisabled然后它在其中找到CompanyIds所有CompaniesCity(通过使用现有的Read model或通过维护CityIdsx的私有状态CompanyIds)并向每个人发送一个DisableCompany命令。

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

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

于 2017-10-20T13:16:06.363 回答
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;
            ...
        });
   }

}

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

于 2017-10-21T02:05:22.987 回答
0

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

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

这个规则应该在读端实现。读取模型中的 Company 将具有 2 个属性:第一个是Enabled,它直接从域映射;第二个是EnabledEffective,可根据 Company 的Enabled值和 City 的Enabled值计算得出。当发生 CityDisabled 事件时,读取模型的事件处理程序会遍历读取模型中 City 的所有 Companies,并将其EnabledEffective属性设置为false;当发生 CityEnabled 事件时,处理程序将 City 的每个 Company 的EnabledEffective属性设置回其自己的Enabled值。它是EnabledEffective您将在 UI 中使用的属性。

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

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

于 2017-10-23T10:18:10.323 回答