加载聚合对域事件的反应
Loading aggregates on reacting to domain events
我正在使用领域驱动设计和事件溯源来实现一个应用程序。我将所有域事件存储在 SQL 服务器中的 DomainEvents table 中。
我有以下聚合:
- City
+ Id
+ Enable()
+ Disable()
- Company
+ Id
+ CityId
+ Enable()
+ Disable()
- Employee
+ Id
+ CompnayId
+ Enable()
+ Disable()
每一个都封装了自己的领域逻辑和不变量。我将它们设计为单独的聚合,因为一个城市可能有数千(也许更多)公司,而且公司也可能有非常多的员工。如果这些实体属于同一个聚合,我必须将它们加载在一起,这在大多数情况下是不必要的。
调用 Enable 或 Disable 将产生域事件(例如 CityEnabled
、CompanyDisabled
或 EmployeeEnabled
)。这些事件包含启用或禁用实体的主键。
现在我的问题是一个新要求迫使我 enable/disable 所有相关的公司如果一个城市是 enabled/disabled。如果公司是 enabled/disabled.
,则员工也需要同样的要求
在我的事件处理程序中,如果例如 CityDisabled
发生,则会调用该事件处理程序
我需要为属于该城市的每个公司执行DisableCompanyCommand
。
但我怎么知道哪些公司会受到该变化的影响?
我的想法:
查询事件存储是不可能的,因为我不能使用像'where CityId = event.CityId'
这样的条件
让父级知道其子级 ID 并将所有子级 ID 放入父级生成的每个事件中。这也是一个坏主意,因为事件创建者不应该关心以后谁会使用这些事件。所以只有属于正在发生的事件的信息应该在事件中。
为每个公司执行DisableCompanyCommand
。只有匹配 CityId
的公司才会更改其状态。即使我会异步执行此操作,它也会产生巨大的开销,让每个公司都对这些事件产生负担。而且对于每家被禁用的公司,都应重复相同的程序以禁用所有用户。
创建读取模型将ParentIds 映射到ChildIds 并根据事件中的parentId 加载childIds。这听起来是最合适的解决方案,但问题是,我如何在禁用现有公司的同时知道是否创建了新公司?
我对以上任何解决方案都不满意。基本上,问题是确定已发生事件的受影响聚合。
也许您有更好的解决方案?
您所描述的内容可以通过侦听 CityDisabled
事件的 Saga/Process manager 来解决。然后它找到 City
中所有 Companies
的 CompanyIds
(通过使用现有的 Read model
或通过维护 CityIds
x[=11= 的私有状态]) 并向每个人发送一个 DisableCompany
命令。
同样适用于CompanyDisabled
事件,关于禁用Employee
。
P.S。禁用 City/Company/Employee 对我来说似乎是 CRUD,这些似乎不是来自 普通 普遍存在语言的术语,它不是很 DDD-ish 但我认为你的设计是正确的关于这个问题。
您的要求是否意味着您必须在禁用城市时触发 CompanyDisabled 事件?
如果不是 - 并且您的要求只是禁用城市意味着所有公司都禁用,那么您要做的是在您的城市阅读模型投影中监听 CityDisabled 事件并在阅读中将公司标记为禁用模型。 (如果你的要求是为每个城市触发一个事件,那么康斯坦丁的答案就很好)
您的模型更像是一种子/父关系 - 它打破了传统的 "blue book" 思想,但我建议在您的域中用不止一个 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)
很可能您不必明确强制执行诸如“enable/disable 所有相关公司如果城市是 enabled/disabled”之类的规则域(写)端。
如果是这样,则在禁用域内的城市时无需禁用所有公司。正如查尔斯在他的回答中提到的,只需引入一个规则,例如 "a Company is disabled if it is disabled itself (directly) or its City is disabled"。公司及其员工也是如此。
这条规则应该在读取端实现。读取模型中的 Company 将具有 2 个属性:第一个是直接从域映射的 Enabled;第二个是 EnabledEffective,可根据公司的 Enabled 值及其城市的 Enabled 值计算得出。当 CityDisabled 事件发生时,读取模型的事件处理程序遍历读取模型中城市的所有 Companies 并将其 EnabledEffective 属性 设置为 false;当 CityEnabled 事件发生时,处理程序将城市的每个公司的 EnabledEffective 属性 设置回其自己的 Enabled 值。您将在 UI.
中使用的是 EnabledEffective 属性
CompanyEnabled/CompanyDisabled 事件处理(关于员工)的逻辑可能有点复杂,因为您必须同时考虑事件信息和主办城市的 enabled/disabled 状态。
如果域端确实需要 Company/Employee 的(有效)enabled/disabled 状态(例如,影响这些聚合处理其命令的方式),请考虑采用 EnabledEffective 读取端的值并将其与命令对象一起传递。
我正在使用领域驱动设计和事件溯源来实现一个应用程序。我将所有域事件存储在 SQL 服务器中的 DomainEvents table 中。
我有以下聚合:
- City
+ Id
+ Enable()
+ Disable()
- Company
+ Id
+ CityId
+ Enable()
+ Disable()
- Employee
+ Id
+ CompnayId
+ Enable()
+ Disable()
每一个都封装了自己的领域逻辑和不变量。我将它们设计为单独的聚合,因为一个城市可能有数千(也许更多)公司,而且公司也可能有非常多的员工。如果这些实体属于同一个聚合,我必须将它们加载在一起,这在大多数情况下是不必要的。
调用 Enable 或 Disable 将产生域事件(例如 CityEnabled
、CompanyDisabled
或 EmployeeEnabled
)。这些事件包含启用或禁用实体的主键。
现在我的问题是一个新要求迫使我 enable/disable 所有相关的公司如果一个城市是 enabled/disabled。如果公司是 enabled/disabled.
,则员工也需要同样的要求在我的事件处理程序中,如果例如 CityDisabled
发生,则会调用该事件处理程序
我需要为属于该城市的每个公司执行DisableCompanyCommand
。
但我怎么知道哪些公司会受到该变化的影响?
我的想法:
查询事件存储是不可能的,因为我不能使用像'where CityId = event.CityId'
这样的条件让父级知道其子级 ID 并将所有子级 ID 放入父级生成的每个事件中。这也是一个坏主意,因为事件创建者不应该关心以后谁会使用这些事件。所以只有属于正在发生的事件的信息应该在事件中。
为每个公司执行
DisableCompanyCommand
。只有匹配CityId
的公司才会更改其状态。即使我会异步执行此操作,它也会产生巨大的开销,让每个公司都对这些事件产生负担。而且对于每家被禁用的公司,都应重复相同的程序以禁用所有用户。创建读取模型将ParentIds 映射到ChildIds 并根据事件中的parentId 加载childIds。这听起来是最合适的解决方案,但问题是,我如何在禁用现有公司的同时知道是否创建了新公司?
我对以上任何解决方案都不满意。基本上,问题是确定已发生事件的受影响聚合。
也许您有更好的解决方案?
您所描述的内容可以通过侦听 CityDisabled
事件的 Saga/Process manager 来解决。然后它找到 City
中所有 Companies
的 CompanyIds
(通过使用现有的 Read model
或通过维护 CityIds
x[=11= 的私有状态]) 并向每个人发送一个 DisableCompany
命令。
同样适用于CompanyDisabled
事件,关于禁用Employee
。
P.S。禁用 City/Company/Employee 对我来说似乎是 CRUD,这些似乎不是来自 普通 普遍存在语言的术语,它不是很 DDD-ish 但我认为你的设计是正确的关于这个问题。
您的要求是否意味着您必须在禁用城市时触发 CompanyDisabled 事件?
如果不是 - 并且您的要求只是禁用城市意味着所有公司都禁用,那么您要做的是在您的城市阅读模型投影中监听 CityDisabled 事件并在阅读中将公司标记为禁用模型。 (如果你的要求是为每个城市触发一个事件,那么康斯坦丁的答案就很好)
您的模型更像是一种子/父关系 - 它打破了传统的 "blue book" 思想,但我建议在您的域中用不止一个 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)
很可能您不必明确强制执行诸如“enable/disable 所有相关公司如果城市是 enabled/disabled”之类的规则域(写)端。
如果是这样,则在禁用域内的城市时无需禁用所有公司。正如查尔斯在他的回答中提到的,只需引入一个规则,例如 "a Company is disabled if it is disabled itself (directly) or its City is disabled"。公司及其员工也是如此。
这条规则应该在读取端实现。读取模型中的 Company 将具有 2 个属性:第一个是直接从域映射的 Enabled;第二个是 EnabledEffective,可根据公司的 Enabled 值及其城市的 Enabled 值计算得出。当 CityDisabled 事件发生时,读取模型的事件处理程序遍历读取模型中城市的所有 Companies 并将其 EnabledEffective 属性 设置为 false;当 CityEnabled 事件发生时,处理程序将城市的每个公司的 EnabledEffective 属性 设置回其自己的 Enabled 值。您将在 UI.
中使用的是 EnabledEffective 属性CompanyEnabled/CompanyDisabled 事件处理(关于员工)的逻辑可能有点复杂,因为您必须同时考虑事件信息和主办城市的 enabled/disabled 状态。
如果域端确实需要 Company/Employee 的(有效)enabled/disabled 状态(例如,影响这些聚合处理其命令的方式),请考虑采用 EnabledEffective 读取端的值并将其与命令对象一起传递。