C# 提交数据时的CQR和冲突

C# 提交数据时的CQR和冲突,c#,asp.net-mvc,cqrs,event-sourcing,C#,Asp.net Mvc,Cqrs,Event Sourcing,因此,我最近开始阅读关于CQR/事件来源的文章,它看起来非常有趣。然而,我就是不能把我的头围绕在一个似乎适合自己的情况下 假设我们有此控制器操作: public ActionResult UpdateCustomerName(int customerId, string newName) { var aggregateRoot = _customerQueryService.GetCustomer(customerId); _bus.Send(new UpdateCustomer

因此,我最近开始阅读关于CQR/事件来源的文章,它看起来非常有趣。然而,我就是不能把我的头围绕在一个似乎适合自己的情况下

假设我们有此控制器操作:

public ActionResult UpdateCustomerName(int customerId, string newName) {
    var aggregateRoot = _customerQueryService.GetCustomer(customerId);
    _bus.Send(new UpdateCustomerNameCommand{Customer = aggregateRoot, NewName = newName});

    return View();
}
这就是我在一些示例中看到的代码。在命令处理程序中检测冲突非常简单,尝试一些自动合并也是如此。我没有看到,并且感到困惑的是,当冲突发生并且无法解决时,该控制器如何优雅地通知用户,从而导致UpdateCustomerNameCommand被拒绝


我们不需要以某种方式通知用户吗?

检测冲突的典型解决方案是在您提交的命令中使用预期版本的事件流

在客户机窗体中,查询要显示的信息时,请确保还获得聚合根或事件流的当前版本。然后,在提交命令时,将此版本设置为预期版本

如果预期版本与当前事件流版本不匹配,则命令处理逻辑可以检测冲突。根据命令和所需的行为,可以尝试以下几项:

如果新命令与任何内容(例如添加订单行)没有冲突,您可以简单地执行它。 您可以尝试使用自定义逻辑将其效果与自预期版本以来所做的更改合并。 您可以发出特定于冲突的事件,该事件可能使用户能够解决冲突。 您可能会使命令失败。 这里没有通用配方。这实际上取决于冲突的语义含义以及用户希望如何解决此类冲突。如果冲突的一部分可以自动解决,请执行此操作,其余部分由用户自行解决

请注意,这样的情况不是错误。如果您希望允许用户同时处理一些事情,并且您和您的用户知道您正在一个最终一致的世界中工作,那么您就可以使用它们

问题编辑后更新: 您给出的新示例有点做作,因为它没有揭示此更改的意图,也没有揭示它与任何业务规则或流程的相关性,但我将使用一个上下文来运行它,在这个上下文中,通过稍微调整它,意图是非常相关的。让我们假设该软件在负责处理的组织中使用

因此,我们可能有以下意图揭示命令可供处理因法院命令、婚姻或离婚而产生的获准姓名变更请求的文员使用:

ChangeLegalNameToMarriedName(... command specific info ...);
ChangeLegalNameToDivorcedName(... command specific info ...);
ChangeLegalNameToNaturalizedName(... command specific info ...);
ChangeLegalNameToNewGenderName(... command specific info ...);
... others
在处理该命令时,这些命令中的每一个都可能会调用一些不同的业务逻辑,例如,在接收到ChangeLegalNameToDivorcedName命令时,确保此人的状态为“已婚”

此外,在进行变更后,可能会启动其他需要进行的流程。也就是说,做出变更后生成的LegalNameChangedToNaturalizedName事件可能会启动向相关移民官员发送电子邮件的业务流程。LegalNameChangedToDivorcedName事件可能会启动一个流程,通知所有当地约会机构潜在的新客户;-

因此,遵循您的示例,假设办事员1刚刚提交了一个具有5个预期聚合版本的CuxEngalNoWangGeDelNeNd命令,并且我们考虑了两个不同的潜在冲突情况:

职员2同时处理了相同的请求并发送了相同的命令。 由于聚合上的命令通常是以事务方式处理的,因此假设首先处理了cler2的命令,并成功地将聚合版本提升到6。当职员1的命令得到处理时,我们检测到一个潜在的冲突。通过检查自版本5以来生成的事件,我们可以看到LegalNameChangedToNewGenderName存在,其内容与Cler1提交的命令匹配。因此,根据我们的业务规则,我们会执行以下操作:1跳过执行职员1的请求2对LegalNameChangeProcess聚合执行RegisterDuplicateNameChangeRequestProcessingAttempt命令,这反过来可能会生成一个事件,供主管检查内部流程的错误

同时,处理了一个具有不同内容的合法名称更改命令。对本组织来说,这将是一种特殊和可能令人担忧的情况。这将导致采取行动解决可能中断的内部流程和可能欺诈的法定名称更改尝试。可以做的是1使用补偿命令还原由第一个命令引起的人名,将其标记为需要解析。2在LegalNameChangeProcess聚合上执行ResolventiallyFrauduleNetleGalNameChange命令

如果,如您提供的示例所示,没有有价值的bu 与客户名称等相关联的siness逻辑,不在应用程序的该部分使用CQR,只需使用CRUD即可。只在能增加真正价值的地方使用它。参见此处的讨论


还请注意,UI/客户端可以接收生成的事件,这样用户就可以在编辑时收到名称更改的通知。显然,这只会降低比赛的可能性,而不会阻止比赛。如果在您的示例中,比赛的发生意味着不应执行名称更改命令,则可以生成名称更改拒绝事件,可以使用命令和事件上的相关id等将冲突返回给用户。

检测冲突的典型解决方案是在您提交的命令中使用预期版本的事件流

在客户机窗体中,查询要显示的信息时,请确保还获得聚合根或事件流的当前版本。然后,在提交命令时,将此版本设置为预期版本

如果预期版本与当前事件流版本不匹配,则命令处理逻辑可以检测冲突。根据命令和所需的行为,可以尝试以下几项:

如果新命令与任何内容(例如添加订单行)没有冲突,您可以简单地执行它。 您可以尝试使用自定义逻辑将其效果与自预期版本以来所做的更改合并。 您可以发出特定于冲突的事件,该事件可能使用户能够解决冲突。 您可能会使命令失败。 这里没有通用配方。这实际上取决于冲突的语义含义以及用户希望如何解决此类冲突。如果冲突的一部分可以自动解决,请执行此操作,其余部分由用户自行解决

请注意,这样的情况不是错误。如果您希望允许用户同时处理一些事情,并且您和您的用户知道您正在一个最终一致的世界中工作,那么您就可以使用它们

问题编辑后更新: 您给出的新示例有点做作,因为它没有揭示此更改的意图,也没有揭示它与任何业务规则或流程的相关性,但我将使用一个上下文来运行它,在这个上下文中,通过稍微调整它,意图是非常相关的。让我们假设该软件在负责处理的组织中使用

因此,我们可能有以下意图揭示命令可供处理因法院命令、婚姻或离婚而产生的获准姓名变更请求的文员使用:

ChangeLegalNameToMarriedName(... command specific info ...);
ChangeLegalNameToDivorcedName(... command specific info ...);
ChangeLegalNameToNaturalizedName(... command specific info ...);
ChangeLegalNameToNewGenderName(... command specific info ...);
... others
在处理该命令时,这些命令中的每一个都可能会调用一些不同的业务逻辑,例如,在接收到ChangeLegalNameToDivorcedName命令时,确保此人的状态为“已婚”

此外,在进行变更后,可能会启动其他需要进行的流程。也就是说,做出变更后生成的LegalNameChangedToNaturalizedName事件可能会启动向相关移民官员发送电子邮件的业务流程。LegalNameChangedToDivorcedName事件可能会启动一个流程,通知所有当地约会机构潜在的新客户;-

因此,遵循您的示例,假设办事员1刚刚提交了一个具有5个预期聚合版本的CuxEngalNoWangGeDelNeNd命令,并且我们考虑了两个不同的潜在冲突情况:

职员2同时处理了相同的请求并发送了相同的命令。 由于聚合上的命令通常是以事务方式处理的,因此假设首先处理了cler2的命令,并成功地将聚合版本提升到6。当职员1的命令得到处理时,我们检测到一个潜在的冲突。通过检查自版本5以来生成的事件,我们可以看到LegalNameChangedToNewGenderName存在,其内容与Cler1提交的命令匹配。因此,根据我们的业务规则,我们会执行以下操作:1跳过执行职员1的请求2对LegalNameChangeProcess聚合执行RegisterDuplicateNameChangeRequestProcessingAttempt命令,这反过来可能会生成一个事件,供主管检查内部流程的错误

同时,处理了一个具有不同内容的合法名称更改命令。对本组织来说,这将是一种特殊和可能令人担忧的情况。这将导致采取行动解决可能中断的内部流程和可能欺诈的法定名称更改尝试。可以做的是1使用补偿命令还原由第一个命令引起的人名,将其标记为需要解析。2在LegalNameChangeProcess聚合上执行ResolventiallyFrauduleNetleGalNameChange命令

如果,就像你提供的例子一样,我 如果没有与客户名称等相关的有价值的业务逻辑,请不要在应用程序的该部分使用CQR,而只需使用CRUD。只在能增加真正价值的地方使用它。参见此处的讨论


还请注意,UI/客户端可以接收生成的事件,这样用户就可以在编辑时收到名称更改的通知。显然,这只会降低比赛的可能性,而不会阻止比赛。如果在您的示例中,比赛的发生意味着不应执行名称更改命令,您可以生成一个名称更改拒绝事件,该事件可以使用命令和事件上的相关id返回给用户。

您能否澄清您正在尝试做什么?基本上,你必须定义一些协议,如何提交。如果任何提交不符合要求,您应该显示一个错误,因为可能存在数据不一致。其中一种方法是先提交。所以2个用户尝试提交。两者都应检查编辑时数据是否未更改。如果数据发生更改-错误,此问题不是CQR特有的,它存在于具有并发用户的每个系统中。要么使用悲观并发并尝试锁定不可扩展的记录,要么使用乐观并发并在发生冲突时警告用户。我认为控制器不应检索聚合并创建依赖项。该命令只需获取其id和预期版本,即创建UpdateCustomerNameView时从读取模型中检索到的id和预期版本?基本上,你必须定义一些协议,如何提交。如果任何提交不符合要求,您应该显示一个错误,因为可能存在数据不一致。其中一种方法是先提交。所以2个用户尝试提交。两者都应检查编辑时数据是否未更改。如果数据发生更改-错误,此问题不是CQR特有的,它存在于具有并发用户的每个系统中。要么使用悲观并发并尝试锁定不可扩展的记录,要么使用乐观并发并在发生冲突时警告用户。我认为控制器不应检索聚合并创建依赖项。该命令只需获取其id和预期版本,就像创建UpdateCustomerNameView时从读取模型中检索到的一样。这是处理这种情况的好计划。以自定义方式合并、失败或处理。如果对并发工作有强烈要求,您应该开发某种事务逻辑,该逻辑将锁定当前编辑的实体,并以实时方式通知其他用户。例如,是的,CQR不会自动解决所有冲突。但是,当冲突发生时,它确实允许逻辑。例如,一个用户可以扮演更重要的角色,而他们的命令总是可以获胜。这是处理这种情况的好计划。以自定义方式合并、失败或处理。如果对并发工作有强烈要求,您应该开发某种事务逻辑,该逻辑将锁定当前编辑的实体,并以实时方式通知其他用户。例如,是的,CQR不会自动解决所有冲突。但是,当冲突发生时,它确实允许逻辑。例如,一个用户可以扮演更重要的角色,而他们的命令总是能够获胜。