所以我最近开始阅读关于CQRS /事件采购的内容,看起来非常有趣。但是,我无法绕过这似乎适合自己的情况。
假设我们有这个控制器动作:
public ActionResult UpdateCustomerName(int customerId, string newName) {
var aggregateRoot = _customerQueryService.GetCustomer(customerId);
_bus.Send(new UpdateCustomerNameCommand{Customer = aggregateRoot, NewName = newName});
return View();
}
我在一些例子中看到过的代码。检测命令处理程序中的冲突非常简单,因此尝试一些自动合并也是如此。我不知道的是,这个控制器如何优雅地通知用户&发生冲突但无法解决时,导致UpdateCustomerNameCommand被拒绝。
我们不需要以某种方式通知用户吗?
答案 0 :(得分:4)
检测冲突的典型解决方案是在您提交的命令中使用事件流的“预期版本”。
在客户端表单中,查询要显示的信息时,确保您还获得聚合根或事件流的当前版本。然后在提交命令时将此版本设置为预期版本。
如果预期版本与当前事件流版本不匹配,则命令处理逻辑可以检测到冲突。根据命令和您期望的行为,可以尝试以下几种方法:
这里没有一般食谱。这实际上取决于冲突在语义上的含义以及用户如何解决此类冲突。如果冲突的(部分)可以自动解决,请执行此操作,其余部分由用户解决。
请注意,这些情况不是错误。如果您希望允许用户同时处理某些事情并且您并且您的用户知道您正在最终一致的世界中工作,则可以预期它们。
问题编辑后更新: 你给出的新例子有点人为,因为它没有透露这个改变的意图,也没有揭示它与任何业务规则或流程的相关性,但是我会使用一个上下文来运行它,在这个上下文中,intent通过调整它而非常重要。一点点。我们假设该软件用于负责处理legal name changes的组织。
因此,我们可能会有以下意图披露可供职员处理授予的法院命令,婚姻或离婚所产生的姓名变更请求的命令:
ChangeLegalNameToMarriedName(... command specific info ...);
ChangeLegalNameToDivorcedName(... command specific info ...);
ChangeLegalNameToNaturalizedName(... command specific info ...);
ChangeLegalNameToNewGenderName(... command specific info ...);
... others
这些命令中的每一个都可能在处理命令时调用某些不同的业务逻辑,例如,收到ChangeLegalNameToDivorcedName
命令时,确保此人的状态为“已婚”。
另外,在进行更改后,可能需要启动其他进程。即更改后生成的LegalNameChangedToNaturalizedName
事件可能会启动向相关移民官发送电子邮件的业务流程。 LegalNameChangedToDivorcedName
活动可能启动一个流程,通知所有当地约会机构有关潜在的新客户; - )。
因此,按照您的示例,假设职员1刚刚提交了ChangeLegalNameToNewGenderName
命令,预期聚合版本为5,我们会考虑两种不同的潜在冲突情况:
职员2同时处理了同一个请求并发送了相同的命令。
因为聚合上的命令通常是事务处理的,所以假设首先处理Clerk 2的命令,成功并将聚合版本碰到6.当处理Clerk 1的命令时,我们检测到潜在的冲突。通过检查自版本5以来生成的事件,我们可以看到LegalNameChangedToNewGenderName
存在,其内容与Clerk 1提交的命令匹配。因此,根据我们的业务规则,我们执行以下操作:(1)跳过执行职员1的请求(2)在RegisterDuplicateNameChangeRequestProcessingAttempt
聚合上执行LegalNameChangeProcess
命令,这反过来可能会导致主管检查内部过程出错的事件。
与此同时,处理了具有不同内容的合法名称更改命令。这对组织来说是一种特殊且可能令人担忧的情况。它应该导致采取行动解决(a)可能破坏的内部流程(b)潜在的欺诈性法律名称变更尝试。可能做的是(1)使用补偿命令恢复由第一个命令引起的人名,将其标记为需要解决。 (2)在ResolvePotentiallyFraudulentLegalNameChange
聚合上执行LegalNameChangeProcess
命令。
如果您提供的示例的情况可能如此,则没有与例如关联的有价值的业务逻辑。客户名称,不要将CQRS用于您应用程序的(该部分),而只需使用CRUD。只有在增加实际价值的地方使用它。参见例如这里的讨论CQRS and CRUD screens。
另请注意,UI /客户端可以接收生成的事件,以便在编辑时可以通知用户名称发生更改的事实。显然,这只会降低比赛的可能性,但并不能阻止比赛。如果在您的示例中,匹配的发生意味着不应执行“名称更改”命令,则可以生成“名称更改被拒绝”事件,该事件可以传递回用户(使用命令和放大器上的相关ID) ;事件)。