旅程2:分解领域
设计停靠站点
“没有石头就没有拱门” --马可波罗
在本章中,我们将对Contoso会议管理系统进行一个高层次的概述。这将帮助您理解应用程序的结构、集成点以及应用程序的各个部分之间的关系。
在这里,我们借用Eric Evans在他的书《领域驱动设计 软件核心复杂性应对之道(Addison-Wesley Professional, 2003)中描述的领域驱动设计(DDD)方法来描述这个高级结构。DDD是成功实现CQRS模式的先决条件虽然还没有达成普遍的共识,但我们团队依然决定按照CQRS社区的惯例使用DDD里众多概念和方法,例如领域、限界上下文,和聚合。参考指南的第1章“上下文中的CQRS”更详细地讨论了DDD和CQRS模式之间的关系。
本章使用的定义
在本章中,我们使用了一些术语,我们将在后面定义它们。有关更多细节和可能的替代定义,请参见参考指南中的第1章“”。
领域(Domain):领域是指Contoso会议管理系统的业务域(参考实现)。第一章“我们的领域:Contoso会议管理系统”概述了这个领域。
限界上下文(Bounded Context):术语限界上下文来自Eric Evans的书。简而言之,Evans引入这个概念是为了将一个大型的、复杂的系统分解成更易于管理的部分。大型系统由多个限界上下文组成。每个限界上下文都是自己包含领域模型的上下文,并且有它自己的通用语言(Ubiquitous Language)。您还可以将限界上下文看做明确定义了一致性边界的自主业务组件。一个限界上下文通常通过引发事件(Raising Events)与另一个限界上下文通信。
上下文映射:根据Eric Evans的说法,您应该“描述模型之间的联系点,明确所有通信需要的转换,并突出任何共享的内容。”这个练习产生了所谓的上下文映射,它有几个用途,包括提供整个系统的概览,以及帮助人们理解不同的限界上下文如何相互交互的细节。
会议系统中的限界上下文
订单和注册限界上下文:在订单和注册限界上下文中包含预订、付款和注册项。当注册用户与系统交互时,系统创建一个订单来管理预订、付款和注册。订单包含一个或多个订单项。
预订是在会议上临时预订一个或多个座位。当注册用户开始订购会议上的一些座位时,系统会为这些座位创建预订。然后,其他用户无法预订这些座位。预订保留15分钟,在此期间,登录人可以通过支付座位的费用完成订购过程。如果用户没有在15分钟内付款,系统将删除该预订,其他用户可以继续预订这些座位。会议管理限界上下文:在这个限界上下文中,业务客户可以创建新的会议并管理它们。在业务客户创建新会议之后,他可以使用电子邮件和访问代码来访问查看会议的详细信息。当业务客户创建会议时,系统生成访问代码。
业务客户可以指定以下关于会议的信息:- 名称、描述和slug(用于访问会议的URL的一部分)。
- 会议的开始和结束日期。
- 会议提供的不同座位类型和配额。
此外,业务客户可以通过发布或取消发布来控制会议在公共网站上的可见性。
业务客户还可以使用会议管理网站查看订单和参会者列表。
支付限界上下文:支付限界上下文负责管理会议管理系统和外部支付系统之间的交互。它将必要的付款信息转发给外部系统,并接收付款被接受或拒绝的结果。它将支付的成功或失败报告给会议管理系统。一开始,支付限界上下文将假定业务客户在第三方支付系统中有一个帐户(尽管不一定是商家帐户),或者业务客户将接受发票支付。
旅程中未包含的几个限界上下文
虽然这几个限界上下文没有进入Contoso会议管理系统的最终版本,但是做了一些工作。社区成员正在开发这些以及一些其他功能,任何带外发布和更新都将在“CQRS之旅”网站上公布。如果您想对这些限界上下文或系统的任何其他方面有所贡献,请访问项目“CQRS Journey”网站或通过[email protected]让我们知道。
折扣限界上下文: 处理会议座位的购买管理和应用折扣。该会议与主系统三个已存在的限界上下文集成在一起。
偶尔断开连接的会议管理客户端:这个限界上下文用于处理会议现场的管理,具有处理标签打印、记录参会者到达情况和额外座位销售的功能。
提交和进度管理限界上下文:用于处理使用Node.js编写的论文提交和会议事件调度。
Contoso会议管理系统的上下文映射
图1和后面的列表显示上下文映射,该映射显示构成完整系统的不同限界上下文之间的关系,因此它提供了系统如何组合在一起的高级概述。尽管这个上下文映射看起来非常简单,但是这些限界上下文的实现,以及更重要的是它们之间的交互,都是相对复杂的。这使我们能够遭遇并处理CQRS模式和Event Sourcing的广泛问题,并有了一个丰富的源头来获取很多宝贵的经验和教训。
图1显示了构成Contoso会议管理系统的三个限界上下文。图中的箭头表示它们之间的事件数据流。
下面的列表提供了关于图1中的箭头的更多信息。您可以在讨论单独限界上下文的章节中找到更多的细节。
- 在创建、更新或发布会议时报告的事件。创建或更新座位类型时报告的事件。
- 创建或更新订单时报告的事件。当与会者被分配到座位时报告的事件。
- 要求付款。
- 确认付款的成功或失败。
为什么选择这些限界上下文?
在旅程(开发)的规划(初期)阶段,很明显,这些是领域中的自然划分,可以包含各自独立的领域模型。其中一些划分比其他的更容易识别。例如,很明显,会议管理限界上下文独立于领域的其他部分。它具有与定义会议和座位类型相关的明确定义的职责,以及与应用程序其他部分的明确定义的集成点。
另一方面,我们花了一些时间才意识到订单和注册限界上下文与支付限界上下文是分开的。例如,直到应用程序的V2发行版,当OrderPaymentConfirmed事件成为OrderConfirmed事件时,与支付相关的所有概念才从订单和注册上下文中消失。
更实际,从旅途的角度来看,我们想要一组限界上下文,使我们能够发布一个可工作的应用程序并包含一些核心的功能,它可以使我们去探索许多不同的实现模式:CQRS, CQRS/ES,以及与传统的集成,例如CRUD风格的限界上下文。