当人类用户真实测试系统的这一部分时,不太会注意到这种效果,因为发出命令的时间间隔要长得多,这使得消息不太可能无序地到达。
团队考虑了两种方法来确保消息以正确的顺序到达。
第一个方法是使用消息会话,这是Azure服务总线的一个特性。如果您使用消息会话,这将确保会话内的消息以与它们发送时相同的顺序传递。
第二种方法是修改应用程序中的处理程序,通过使用发送消息时添加到消息中的序列号或时间戳来检测无序消息。如果接收处理程序检测到一条无序消息,它将拒绝该消息,并在处理了在被拒绝消息之前发送的消息之后,将其放回稍后处理的队列或Topic。
在这种情况下,首选的解决方案是使用Azure服务总线消息会话,因为这只需要对现有代码进行更少的更改。这两种方法都会给消息传递带来一些额外的延迟,但是团队并不认为这会对系统的性能产生显著的影响。
实现细节本节描述订单和注册限界上下文的实现的一些重要功能。您可能会发现拥有一份代码拷贝很有用,这样您就可以继续学习了。您可以从Download center下载一个副本,或者在GitHub上查看存储库:https://github.com/mspnp/cqrs-journey-code。您可以从GitHub上的Tags页面下载V2版本的代码。
备注:不要期望代码示例与参考实现中的代码完全匹配。本章描述了CQRS过程中的一个步骤,随着我们了解更多并重构代码,实现可能会发生变化。 **添加对“不需要支付的订单”的支持做出这一改变有三个具体的目标,它们都是相关的。我们希望:
修改RegistrationProcessManager类和相关聚合,以处理不需要支付的订单。
修改UI中的导航,当订单不需要支付时跳过付款步骤。
确保系统在升级到V2之后能够正确地工作,包括使用新事件和旧事件。
RegistrationProcessManager类的更改在此之前,RegistrationProcessManager类在收到来自UI的注册者已完成支付的通知后发送了一个ConfirmOrderPayment命令。现在,如果有一个不需要支付订单,UI将直接向订单聚合发送一个ConfirmOrder命令。如果订单需要支付,RegistrationProcessManager类在从UI接收到成功支付的通知后,再向订单聚合发送一个ConfirmOrder命令。
Jana(软件架构师)发言:注意,命令的名称已从ConfirmOrderPayment更改为ConfirmOrder。这反映了订单不需要知道任何关于付款的信息。它只需要知道订单已经确认。类似地,现在有一个新的OrderConfirmed事件用于替代旧的OrderPaymentConfirmed事件。
当订单聚合接收到ConfirmOrder命令时,它将引发一个OrderConfirmed事件。除被持久化外,该事件还由以下对象处理:
OrderViewModelGenerator类,它在其中更新读取模型中的订单状态。
SeatAssignments聚合,在其中初始化一个新的SeatAssignments实例。
RegistrationProcessManager类,它在其中触发一个提交座位预订的命令。
UI的更改UI中的主要更改是在RegistrationController MVC控制器类中的SpecifyRegistrantAndPaymentDetails action里的。之前,此action方法返回InitiateRegistrationWithThirdPartyProcessorPayment(action result)。现在,如果Order对象的新IsFreeOfCharge属性为true,它将返回一个CompleteRegistrationWithoutPayment(action result)。否则,它返回一个CompleteRegistrationWithThirdPartyProcessorPayment(action result)。
[HttpPost] public ActionResult SpecifyRegistrantAndPaymentDetails(AssignRegistrantDetails command, string paymentType, int orderVersion) { ... var pricedOrder = this.orderDao.FindPricedOrder(orderId); if (pricedOrder.IsFreeOfCharge) { return CompleteRegistrationWithoutPayment(command, orderId); } switch (paymentType) { case ThirdPartyProcessorPayment: return CompleteRegistrationWithThirdPartyProcessorPayment(command, pricedOrder, orderVersion); case InvoicePayment: break; default: break; } ... }CompleteRegistrationWithThirdPartyProcessorPayment将用户重定向到ThirdPartyProcessorPayment action,CompleteRegistrationWithoutPayment方法将用户直接重定向到ThankYou action。
数据迁移