我正在研究 CQRS,并且正在寻找有关如何在最终一致的系统中解决客户端读取问题的文章。例如,考虑一个网上商店,用户可以在其中将商品添加到他们的购物车中。如果“AddItemToCart”命令的实际处理是异步完成的,如何确保客户端显示购物车中的商品?我了解基于域事件异步调度命令和更新读取模型异步的原则,但是我看不到从客户端的角度来看这是如何处理的。
3 回答
有几种不同的方法。
等待用户直到一致
只需轮询服务器,直到您更新读取模型。这与 Ben 所展示的相似。
通过 2PC 确保一致性
你有一个支持 DTC 的队列;并且您的命令首先放在那里。然后他们是;执行、发送事件、更新读取模型;都在一个事务中。但是,您实际上并没有通过这种方法获得任何收益,因此请不要这样做。
骗客户
将读取的模型放在客户端的本地存储中,并在发送相应事件时更新它们——但无论如何您都期待这个事件,所以您已经更新了购物车的 javascript 视图。
我建议您查看Microsoft Patterns & Practices 团队对 CQRS 的指导。尽管这仍在进行中,但他们已经为您提出的问题提供了一种解决方案。
他们对需要反馈的命令的方法是异步提交命令,重定向到另一个控制器操作,然后轮询读取模型以获取预期的更改或发生超时。这是使用 Post-Redirect-Get 模式,该模式与浏览器的前进和后退导航按钮配合得更好,并在 MVC 控制器开始轮询之前为基础架构提供更多时间来处理命令。
RegistrationController中使用 ASP.NET MVC 4 异步控制器的示例代码。
[HttpGet]
[OutputCache(Duration = 0, NoStore = true)]
public Task<ActionResult> SpecifyRegistrantAndPaymentDetails(Guid orderId, int orderVersion)
{
return this.WaitUntilOrderIsPriced(orderId, orderVersion)
.ContinueWith<ActionResult>(
...
);
}
...
private Task<PricedOrder> WaitUntilOrderIsPriced(Guid orderId, int lastOrderVersion)
{
return
TimerTaskFactory.StartNew<PricedOrder>(
() => this.orderDao.FindPricedOrder(orderId),
order => order != null && order.OrderVersion > lastOrderVersion,
PricedOrderPollPeriodInMilliseconds,
DateTime.Now.AddSeconds(PricedOrderWaitTimeoutInSeconds));
}
我可能会使用 AJAX 轮询,而不是在服务器上阻止 Web 请求。
重定向后获取
您希望 save 命令在调用 Get 之前按时执行。如果命令需要 10 秒才能在后端完成,但 Get 在 1 秒内被调用怎么办?
本地存储
在命令执行时将命令的结果存储在客户端上,您假设命令将无错误地通过。如果后端在处理命令时遇到错误怎么办?那么你在本地拥有的东西并不一致。
轮询
轮询似乎是实际上符合最终一致性的选项;你不是假装或假设。作为页面的一部分,您的轮询机制可以是异步的,例如购物车页面组件轮询直到它获得更新而不刷新页面。
回调
如果客户端能够接收此类,您可以引入类似网络挂钩的东西来向客户端进行回调。通过在命令被后端接受后提供关联Id,一旦命令完成处理,后端可以将命令的状态以及命令是否成功通过的关联Id通知给前端. 使用这种方法不需要任何类型的轮询。