我有一个简单的两个聚合根和一个常规实体的检查域。
Tenant
,UserGroup
和User
在此特定示例中Tenant
和User
弥补了两个 AggregateRoots 。
当从UI /服务层收到命令时,它会到达操作只写域的命令处理程序。
您可以说User
根本不应该是 AggregateRoot ,但由于它会被其他人引用,因此它不能是常规实体。 (是?)
这两个AggregateRoot需要进行通信。如果不属于User
,则无法创建UserGroup
,Tenant
是User.Create(TenantId, UserGroupId)
的有界上下文中的实体。据推测,我们可以通过构造函数创建一个用户,因为它是一个简单的约束。 DomainEvent
它使用Date,AggregateVersion和AggregateId(用户)生成Tenant
。现在我们到了模糊的部分。
打开将此事件提交到商店,此事件将被广播到总线(内存,无论如何)。在这一点上,域的事件处理程序(类似于命令处理程序)捕获用户创建并通知/操纵UserGroup
的{{1}}以添加UserId
?
我对解决这个问题的想法是否会走向完全错误的方向?
答案 0 :(得分:7)
您可能正在寻找Saga
。
简单地说:一个saga可以实现为一个事件处理程序,它可以侦听特定事件并向不同的聚合根或甚至跨上下文边界发出命令。
在您的情况下,它可能如下所示:
public class RegisterUserSaga : Handles<UserCreated>
{
public void Handle<UserCreated>(UserCreated evnt) {
var tenantId = // you probably know how to find this
var groupId = // same here
var command = new RegisterUserForTenant(evnt.UserId, tenantId, groupId);
Bus.Send(command);
}
}
通过Rinat Abdullin在this article了解更多关于传奇的信息,或者通过Udi Dahan观看"CQRS, race conditions, and sagas - oh my!"
<强>更新强>
在我们在评论中进行了扩展讨论之后,我将尝试从不同的角度展示这是如何工作的(前面的伪代码)。这有望为可能的解决方案提供更多信息:
// Aggregates:
Tenant
Guid TenantId
List<Guid> UserGroups
UserGroup
Guid UserGroupId
List<Guid> Users
User
Guid UserId
Some more details
// Commands:
RequestRegistration(userId, userGroupId, user details)
CreateUser(userId, user details)
AddUserToGroup(userId, userGroupId)
// The initial command would be:
RequestRegistration (leading to a RegistrationRequested event)
// The Saga handles the RegistrationRequested and all subsequent events
UserRegistrationSaga
Handle(RegistrationRequested)
-> send CreateUser command (which eventually leads to a UserCreated event)
Handle(UserCreated)
-> send AddUserToGroup command (-> UserAddedToGroup event)
Handle(UserAddedToGroup)
-> Done