CQRS之旅——旅程3(订单和注册限界上下文) (6)

Web应用通过命令总线(Command Bus)向写模型(Write Model)发送命令。命令总线是系统中的可靠消息传递基础设施组件。在这个场景中,它异步将命令发送给接受者,并且只发送一次。

RegistrationController类可以向写模型(Write Model)发送RegisterToConference命令,此命令发送一个请求,请求在会议上注册一个或多个席位,然后,RegistrationController类轮询读模型(Read Model),以发现注册请求是否成功。参见第6节:“轮询读模型(Read Model)”以获得更多细节。

下面的代码示例展示了RegistrationController如何发送RegisterToConference命令:

var viewModel = this.UpdateViewModel(conferenceCode, contentModel); var command = new RegisterToConference { OrderId = viewModel.Id, ConferenceId = viewModel.ConferenceId, Seats = viewModel.Items.Select(x => new RegisterToConference.Seat { SeatTypeId = x.SeatTypeId, Quantity = x.Quantity }).ToList() }; this.commandBus.Send(command);

备注:所有的命令都是异步发送的,不需要等待返回。

处理命令

命令处理程序在命令总线上注册,然后,命令总线可以将命令转发给正确的处理程序。

OrderCommandHandler类处理从UI发送的RegisterToConference命令。通常,处理程序负责调用领域里的某些业务逻辑,并将某些状态更新持久化到数据存储中。

下面的代码示例展示了OrderCommandHandler类如何处理RegisterToConference命令:
```c#
public void Handle(RegisterToConference command)
{
var repository = this.repositoryFactory();

using (repository as IDisposable) { var seats = command.Seats.Select(t => new OrderItem(t.SeatTypeId, t.Quantity)).ToList(); var order = new Order(command.OrderId, Guid.NewGuid(), command.ConferenceId, seats); repository.Save(order); }

}
```

在领域中初始化业务逻辑

在前面的代码示例中,OrderCommandHandler类创建了一个新的Order实例。Order对象是一个聚合根,它的构造函数包含初始化领域逻辑的代码。有关此聚合根执行哪些操作的详细信息,请参阅下面的“在写模型内部”一节。

把改动持久化

在前面的代码示例中,命令处理程序通过调用repository类中的Save方法来持久化一个新的订单(Order)聚合。这个Save方法还将在命令总线(Command Bus)上发布订单(Order)聚合引发的各种事件。

轮询读模型(Read Model)

要向用户提供反馈,UI端必须能够检查RegisterToConference命令是否成功。与系统中的所有命令一样,此命令异步执行,不返回结果。UI端通过轮询读模型(Read Model)来检查命令是否成功。

下面的代码示例展示了一个初始实现,其中RegistrationController类里的WaitUntilUpdated方法轮询读模型,直到它发现订单已经被持久化成功或超时。
```c#
[HttpPost]
public ActionResult StartRegistration(string conferenceCode, OrderViewModel contentModel)
{
...

this.commandBus.Send(command); var draftOrder = this.WaitUntilUpdated(viewModel.Id); if (draftOrder != null) { if (draftOrder.State == "Booked") { return RedirectToAction("SpecifyPaymentDetails", new { conferenceCode = conferenceCode, orderId = viewModel.Id }); } else if (draftOrder.State == "Rejected") { return View("ReservationRejected", viewModel); } } return View("ReservationUnknown", viewModel);

}
后来,团队用Post-Redirect-Get模式的实现替换了这种检查系统是否保存订单的机制。下面的代码示例展示了StartRegistration方法的新版本。 > 备注:更多关于Post-Redirect-Get模式的信息,请在Wikipedia查看[Post/Redirect/Get]()c#
[HttpPost]
public ActionResult StartRegistration(string conferenceCode, OrderViewModel contentModel)
{
...

this.commandBus.Send(command); return RedirectToAction("SpecifyRegistrantDetails", new { conferenceCode = conferenceCode, orderId = command.Id });

}
新的StartRegistration action方法现在发送命令后立即重定向到SpecifyRegistrantDetails action。下面的代码示例显示了SpecifyRegistrantDetails action如何在返回视图之前轮询数据库中的订单。c#
[HttpGet]
public ActionResult SpecifyRegistrantDetails(string conferenceCode, Guid orderId)
{
var draftOrder = this.WaitUntilUpdated(orderId);

...

}
```
新方法的优点:使用Post-Redirect-Get模式而不是StartRegistration post action,能让浏览器的“前进”和“后退”导航按钮工作的更好,并在控制器开始轮询之前给命令处理程序更多时间来处理命令。

译者注:此文章编写时间较早。文中的UI端指的是asp.net mvc web应用程序。现在流行的方式是前后端分离。后端入口服务一般是一个web api程序。而且基于轮询的方案也不太理想。可以在web api端通过消息总线订阅数据持久化的各类事件,当在数据存储层引发这些事件时。web api中的事件处理程序可以接收到视图模型改变的数据。再将数据通过SignalR或web socket方式推送至前端。

在写模型内部 聚合

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsfsgw.html