Jana(软件架构师)发言:
您不能将此信息存储在HTTP Session中,因为注册者可能在请求座位和完成订单之间离开站点。
进一步的结果是,读端的底层存储不能是简单的SQL视图,因为它包含的数据没有存储在写端的底层表存储中。因此,必须使用事件将此信息传递给读取方。
下面的架构图显示了订单(Order)和可用座位(SeatsAvailability)聚合使用的所有命令和事件,以及订单(Order)聚合如何通过引发事件将更改推送到读取端。
OrderViewModelGenerator类处理OrderPlaced、OrderUpdated、OrderPartiallyReserved、OrderRegistrantAssigned和OrderReservationCompleted事件,并使用DraftOrder和DraftOrderItem实例将更改持久化到视图表中。
Gary(CQRS专家)发言:
如果您提前阅读第5章“准备发行V1版本”,您将看到团队扩展了事件的使用,并迁移了订单和注册上下文,以使用事件源。
在实现写模型时,应该尽量确保命令很少失败。这将提供最佳的用户体验,并使您的应用程序更容易实现异步行为。
团队采用的一种方法是使用ASP.NET MVC中的模型验证功能。
您应该小心区分系统错误和业务错误。系统错误的例子包括:
由于消息传递基础设施出现故障,无法传递消息。
由于与数据库的连接问题,数据没有持久化。
在许多情况下,特别是在云中,您可以通过重试操作来处理这些错误。
Markus(软件开发人员)发言:
来自Microsoft patterns & practices的Transient Fault Handling Application Block的设计目的是使任何Transient Fault更容易实现一致的重试行为。它提供了一组针对Azure SQL数据库、Azure存储、Azure缓存和Azure服务总线的内置检测策略,还允许您定义自己的策略。类似地,它提供了一组方便的内置重试策略,并支持自定义策略。更多信息请参见The Transient Fault Handling Application Block
业务错误应该有预先定好的逻辑响应。例如:
如果系统因为没有剩余的座位而无法预订座位,那么它应该将请求添加到等待列表中。
如果信用卡支付失败,用户应该有机会尝试另一种信用卡,或者使用发票付款。
Gary(CQRS专家)发言:
您的领域专家应该帮助您识别可能发生的业务失败,并确定您处理它们的方法:使用自动化流程或手动方式。
向注册者显示完成订单所需时间的倒计时器是系统中的业务的一部分,而不仅仅是基础设施的一部分。当注册者创建一个订单并预订座位时,倒计时就开始了。即使登记人离开会议网站,倒计时仍在继续。如果注册用户返回网站,UI必须能够显示正确的倒计时值,因此,保留过期时间是读模型中可用数据的一部分。
实现细节本节描述订单和注册限界上下文的实现的一些重要特性。您可能会发现拥有一份代码副本很有用,这样您就可以继续学习了。您可以从Download center下载一个副本,或者在GitHub上查看存储库中的代码:https://github.com/mspnp/cqrs- jourcode
不要期望代码示例与参考实现中的代码完全匹配。本章描述了CQRS过程中的一个步骤,但是随着我们了解更多并重构代码,实现可能会发生变化。 订单访问代码和记录定位器注册者可能需要检索订单,或者查看订单,或者完成对参会人员座位的分配。这可能发生在不同的web会话中,因此注册者必须提供一些信息来定位以前保存的订单。
下面的代码示例显示Order类如何生成一个新的五个字符的订单访问代码,该代码作为Order实例的一部分被持久化。
public string AccessCode { get; set; } protected Order() { ... this.AccessCode = HandleGenerator.Generate(5); }要检索订单实例,注册者必须提供其电子邮件地址和订单访问代码。系统将使用这两项来定位正确的Order。这是读取端的逻辑。
下面的代码示例来自web应用程序中的OrderController类,展示了MVC控制器如何使用LocateOrder方法向读取端提交查询,以发现唯一的OrderId值。这个Find action将OrderId值传递给一个Display action,该action将订单信息显示给注册者。
[HttpPost] public ActionResult Find(string email, string accessCode) { var orderId = orderDao.LocateOrder(email, accessCode); if (!orderId.HasValue) { return RedirectToAction("Find", new { conferenceCode = this.ConferenceCode }); } return RedirectToAction("Display", new { conferenceCode = this.ConferenceCode, orderId = orderId.Value }); } 倒计时器