CQRS之旅——旅程4(扩展和增强订单和注册限界上下文) (6)

MVC视图使用这个命令类作为它的模型类。下面的代码示例来自SpecifyRegistrantDetails.cshtml文件,它显示了如何填充模型。

@model Registration.Commands.AssignRegistrantDetails ... <div>@Html.LabelFor(model => model.FirstName)</div><div>@Html.EditorFor(model => model.FirstName)</div> <div>@Html.LabelFor(model => model.LastName)</div><div>@Html.EditorFor(model => model.LastName)</div> <div>@Html.LabelFor(model => model.Email)</div><div>@Html.EditorFor(model => model.Email)</div>

Web.config文件根据DataAnnotations属性配置客户端验证,如下面的代码片段所示:

<appSettings> ... <add key="ClientValidationEnabled" value="true" /> <add key="UnobtrusiveJavaScriptEnabled" value="true" /> </appSettings>

服务器端验证发生在发送命令之前的控制器中。下面来自RegistrationController类的代码示例展示了控制器如何使用IsValid属性来验证命令。请记住,这个示例使用的是命令的一个实例作为模型。

[HttpPost] public ActionResult SpecifyRegistrantDetails(string conferenceCode, Guid orderId, AssignRegistrantDetails command) { if (!ModelState.IsValid) { return SpecifyRegistrantDetails(conferenceCode, orderId); } this.commandBus.Send(command); return RedirectToAction("SpecifyPaymentDetails", new { conferenceCode = conferenceCode, orderId = orderId }); }

有关其他示例,请参见RegistrationController类中的RegisterToConference命令和StartRegistration action方法。

更多信息,请参考MSDN上的Models and Validation in ASP.NET MVC。

推送更新到读端

关于订单的一些信息只需要存在于读取端。特别是,关于部分已完成订单的信息只在UI中使用,而不是写端领域模型保存的业务信息的一部分。

这意味着系统不能使用SQL视图作为读取端上的底层存储机制,因为视图不包含它们所基于的表中不存在的数据。

系统将非规范化的订单数据存储在SQL数据库实例中的两个表中:OrdersView和OrderItemsView表。OrderItemsView表包含RequestedSeats列,该列包含仅存在于读取端上的数据。

OrdersView表
列|说明
--|--
OrderId|Order的唯一ID
ReservationExpirationDate|预订座位的过期时间
StateValue|订单的状态,包括:Created, PartiallyReserved, ReservationCompleted, Rejected, Confirmed
RegistrantEmail|预订时填写的Email地址
AccessCode|订单的访问码

OrderItemsView
列|说明
--|--
OrderItemId|订单项的唯一ID
SeatType|预订的座位类型
RequestedSeats|请求预订座位的数量
ReservedSeats|预留座位的数量
OrderId|关联的父Order的ID

要将这些表填充到读模型中,读端需要处理由写端引发的事件,用它们对这些表进行写操作。有关详细信息,请参见上面章节中的架构图。

OrderViewModelGenerator类处理这些事件并更新读端存储库。

public class OrderViewModelGenerator : IEventHandler<OrderPlaced>, IEventHandler<OrderUpdated>, IEventHandler<OrderPartiallyReserved>, IEventHandler<OrderReservationCompleted>, IEventHandler<OrderRegistrantAssigned> { private readonly Func<ConferenceRegistrationDbContext> contextFactory; public OrderViewModelGenerator(Func<ConferenceRegistrationDbContext> contextFactory) { this.contextFactory = contextFactory; } public void Handle(OrderPlaced @event) { using (var context = this.contextFactory.Invoke()) { var dto = new DraftOrder(@event.SourceId, DraftOrder.States.Created) { AccessCode = @event.AccessCode, }; dto.Lines.AddRange(@event.Seats.Select(seat => new DraftOrderItem(seat.SeatType, seat.Quantity))); context.Save(dto); } } public void Handle(OrderRegistrantAssigned @event) { ... } public void Handle(OrderUpdated @event) { ... } public void Handle(OrderPartiallyReserved @event) { ... } public void Handle(OrderReservationCompleted @event) { ... } ... }

下面的代码示例展示ConferenceRegistrationDbContext类:

public class ConferenceRegistrationDbContext : DbContext { ... public T Find<T>(Guid id) where T : class { return this.Set<T>().Find(id); } public IQueryable<T> Query<T>() where T : class { return this.Set<T>(); } public void Save<T>(T entity) where T : class { var entry = this.Entry(entity); if (entry.State == System.Data.EntityState.Detached) this.Set<T>().Add(entity); this.SaveChanges(); } }

Jana(软件架构师)发言:

注意,读端中的这个ConferenceRegistrationDbContext类包含一个Save方法,以保存从写端发送的更改,并通过OrderViewModelGenerator类来调用。

在读端查询

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

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