一位注册者是指在会议上预订座位并支付(订座)费用的人。订购过程分为两个阶段:首先,注册者预订一些座位,然后支付座位的费用来确认预订。如果注册者没有完成付款,预定的座位将在一段固定时间后过期,系统将为其他注册者预留座位。
下图展示了了团队用于探索座位预定的一些早期UI原型图。
订单功能的用户界面原型图
这些UI原型图在几个方面帮助了团队,允许他们:
将核心团队对系统的愿景传达给第三方公司独立团队中的UI设计师。
向开发人员传达领域专家的知识。
使用通用语言提炼,细化术语的定义。
探索“如果发生XXX又怎样XXX”的场景,研究替代方案。
构建基础的系统验收测试套件。
架构此应用程序设计为部署到Microsoft Azure。到旅程的这个阶段,应用程序将包含一个ASP.NET MVC web应用程序和消息处理程序以及领域模型对象。应用程序使用Azure SQL Database实例进行数据存储,读和写两者都包括。应用程序使用Azure Service Bus来进行消息传递。
译者注:鉴于Azure国内版瘸腿,国际版速度奇慢。而且都价格喜人。后续的实战中,架构会根据当前的实际情况进行调整。主要是学习原文的思想。
在研究和测试解决方案时,可以在本地运行它,可以使用Azure compute emulator,也可以直接运行MVC web应用程序,并运行承载消息处理程序和领域域对象的控制台应用程序。在本地运行应用程序时,可以使用本地SQL Server Express数据库,并使用一个在SQL Server Express数据库实现的简单的消息传递基础设施。
有关运行应用程序的选项的更多信息,请参见附录1“发布说明”。
Gary(CQRS专家)发言:CQRS模式的一个经常被援引的优势是,它使您能够独立的伸缩应用程序的读端和写端,以支持不同的使用模式。然而,在这个限界上下文中,来自UI的读操作的数量不太可能超过写操作的数量:这个限界上下文中关注的是创建订单的注册者。因此,读端和写端将部署到同一个Azure工作者角色,而不是部署到两个可以独立伸缩的独立工作者角色。 模式和概念
为了保持简单,团队决定在不使用事件源(Event Sourcing)的情况下先实现第一个限界上下文。当然,他们也确定,如果将来确定事件源能为这个限界上下文带来特定的好处,那么他们将重新考虑这个决定。
备注:有关Event Sourcing如何与CQRS模式关联的描述,请参阅参考指南中的“介绍事件源”。
小组进行的一项重要讨论是选择它们将实现的聚合和实体。以下来自团队白板的图片说明了他们最初的一些想法,以及他们通过一个替代方法(一个真实的会议座位预定场景)来尝试理解这里有什么优缺点。
“我认为开发人员需要收获一个观念,那就是把对象的属性存储在关系型数据库中是不重要的。教会他们避免将领域模型作为关系存储,我认为这样将会更容易介绍和理解领域驱动设计(DDD)和CQRS” --Josh Elster, CQRS Advisors Mail List Gary(CQRS专家)发言:这些图刻意排除了系统如何通过命令和事件处理程序处理命令和事件的细节。这些图主要关注领域中的聚合之间的逻辑关系。
此场景考虑当注册者试图在会议上预订多个座位时会发生什么。系统必须:
检查是否有足够的座位。
记录注册详情。
更新会议预订的座位总数。
我们刻意保持场景简单,以避免在团队检查其他方案时分心。这些示例没有描述这个限界上下文的最终实现。团队考虑的第一种方法(如下图所示)是使用两个分开的聚合。
图中的数字对应于以下步骤:
从UI发送一个命令用来注册参会者X和Y到157号会议,这个命令被路由到一个新的订单(Order)聚合。
订单聚合引发(Raise)一个事件,该事件报告已经创建了一个订单。这个事件被路由到可用座位(SeatsAvailability)聚合。
ID为157的可用座位(SeatsAvailability)聚合是从数据库中取回还原(re-hydrated)的。
可用座位(SeatsAvailability)聚合更新它自己的预定座位总数。
更新后的可用座位(SeatsAvailability)聚合被持久化到数据库中。
ID为4239的新的订单聚合被持久化到数据库中。