第04课:领域建模——架构设计的第一步(上)

第04课:领域建模——架构设计的第一步(上)

在第二篇《深入剖析架构师角色》中我们提到,架构师需要能够从问题领域出发推导出满足业务需求的架构体系,同时又能够从实现方法入手设计出能够满足业务架构需求的技术架构体系,最终实现业务架构和技术架构的统一。在上一篇《架构设计的核心问题》中我们也认识到架构设计的两个层次,即面向问题领域的业务架构和面向解决方案的技术架构。诚然,对于架构师的需求主要体现在技术架构上,但业务架构是实现技术架构的前提,目前一些大型互联网公司对于业务架构师的需求也逐渐增多,下图是来自拉勾的一则业务架构师招聘信息,可作参考。

面向领域思想

在大型团队的系统设计和实现过程中,确保业务架构和技术架构能够统一已经成为系统开发成败的关键。而领域驱动设计(Domain-Driven Design,DDD)为我们提供了一种软件开发方法,强调开发人员与领域专家协作交付业务价值,强调把握业务的高层次方向,也强调系统建模工具和方法以满足技术需求。领域驱动设计思想的核心就是认为系统架构应该是业务架构和技术架构相结合的一种过程,并提供了一系列的设计相关工具和模式确保实现这一过程。在具体讲解领域驱动设计之前,我们先来理解领域的概念。

所谓领域(Domain),即是对现实世界问题的一种统称,是一个组织的业务开展方式,体现一个组织所做的事情以及其中所包含的一切业务范围和所进行的活动,我们在开发软件时面对的就是组织的领域。例如,一个电商网站的领域包含了产品名录、订单、库存和物流的概念,而医疗信息化公司关注挂号、就诊、用药、健康报告等领域。对架构设计而言,领域概念的提出是为了更好的体现系统的业务价值,领域的业务价值在于通过对业务定义的抽象,系统设计和开发提供了有用的领域模型和清晰的模型边界,从而实现更好的用户体验。领域驱动设计也是实现设计微服务架构的基本方法论,我们还将在第八篇《微服务——最热门的架构》中结合微服务的拆分和集成方法对领域驱动设计做进一步展开讨论。

领域驱动设计核心概念

开发人员对领域的思考方法体现在设计的维度上。在领域驱动设计中,有两个主要的设计维度,即设计的策略维度和设计的技术维度。

  • 设计的策略维度

设计的策略维度关注如何设计领域模型以及对领域模型的划分,其目的在于清楚界分不同的系统与业务关注点。策略维度是一个面向业务、具备较高层次的的设计维度,偏重于业务架构的梳理以及考虑如何把业务架构和技术架构相结合的问题。

  • 设计的技术维度

设计的技术维度关注技术实现,从技术的的层面指导我们如何具体地实施领域驱动,关注基于技术设计工具按照领域模型开发软件。显然,技术维度偏向于技术实现,体现了技术架构的设计和展现方式。

设计的策略维度和技术维度相结合提供了一套通用的建模语言和术语,展示基于领域驱动的架构设计方法和实现领域驱动设计的各项关键技术,在本篇中,我们将主要介绍面向领域的策略设计,面向领域的技术设计将在下一篇中介绍。

面向领域的策略设计

策略维度包含领域驱动设计中的一些核心概念,用于抽象业务模型的领域或子域(Sub Domain),用于划分系统边界并考虑系统集成的限界上下文(Boundary Context)以及基于领域驱动设计的特有架构风格都属于这一设计维度。

任何软件系统的发展都是从简单到复杂、从集中到分散的过程。在系统构建初期,我们习惯于构建单一、内聚和全功能式的系统,因为这样的系统就能满足当前业务的需求。而当系统发展到一定阶段,集中化系统已经表现出诸多弊端,功能拆分和服务化思想和实践就会被引入。而当系统继续演进,团队规模也随之增大,由于分工模糊和业务复杂度的不断上升,系统架构逐渐被腐化,直到系统不能承受任何改变,也就到了需要重新拆分的阶段。这一过程给我们的启示就在于将所有东西放在一个系统中是不好的,软件系统的关注点应该清晰划分,并能通过功能拆分降低系统复杂性。

系统拆分方法

系统拆分需要解决两个问题:

  • 如何找到拆分的切入点?
  • 如何对拆分后的功能进行组装?

针对第一个问题,领域驱动设计给出了子域(Sub Domain)的概念。子域作为系统拆分的切入点,其来源往往取决于系统的特征和拆分的需求,如核心功能、辅助性功能、第三方功能等。而对于第二个问题而言,基本的思路就是系统集成,即子域之间通过有效的集成方式确保拆分后的业务功能能够整合到一起构成一个大的业务功能。系统集成的需求与业务需求不同,虽然包含在子域之中,但更多的关注集成的策略和技术体系,在领域驱动设计中,这部分需求及其实现被称为界限上下文(Boundary Context)。

子域

子域的划分虽然因系统而异,但通过对子域的抽象,我们还是可以梳理出通用的分类方法。业界比较认可的分类方法认为,系统中的各个子域可以分成核心域、支撑子域和通用子域三种类型,其中系统中的核心业务属于核心域,专注于业务的某一方面的子域称为支撑子域,可以用于整个业务系统且作为一种基础设施的功能可以归到通用子域。当然,这也只是一家之说,我们可以根据需要建立对子域的抽象模型,为了描述方便,本篇后续内容以上述的分类方法标记系统子域。

界限上下文

子域存在于界限上下文中,这里的界限指的是每个模型概念、属性和操作,在特定边界之内具有特定的含义,这些含义只限于该界限之内。如下图就是一个简单的界限上下文,其中 A 上下文和 B 上下文中都存在 User 对象,但是 B 上下文中的 User 对象不同于 A 上下文中的 User 对象,而 B 上下文中 Account 对象可能基于 A 上下文中的 Role 对象,这时候我们就会发现界限的划分能在很大程度上影响系统的设计和实现。

整合子域与界限上下文的示例结构见下图(来自于移动医疗场景),该图根据业务功能的特性把整个系统拆分成三个主要的子域,分别包含一个核心子域,一个支撑性子域以及一个通用子域,每个子域都有其界限上下文,各个界限上下文之间可以根据需要有效整合从而构成完整的领域。

系统拆分策略

根据子域和界限上下文概念,我们就可以对系统进行拆分。系统拆分的策略可以因地制宜,常见的拆分策略也包括:

  • 根据业务

根据业务进行系统拆分是面向领域策略设计的前提,也是《系统架构设计–程序员向架构师转型之路》一书所推崇的方法。

  • 根据技术架构

根据技术架构拆分系统违背了业务架构驱动技术架构的原则,在对业务梳理尚不完善、系统的策略设计尚不健全的情况下就考虑技术架构和实现方法,往往会导致返工,在不断的系统修改中腐化架构。

  • 根据开发任务分配

据开发任务分配同样不是一个好主意,在系统拆分过程中实际还没有到具体开发资源和时间统筹的阶段,开发任务自然也无从谈起。

  • 一个团队负责一个上下文

但是一个团队一个上下文策略有时候反而是一种有效的拆分策略。团队的构建方式可以是职能团队(Function Team)也可以是特征团队(Feature Team),前者关注于某一个特定职能,如常见的服务端、前端、数据库、UI 等功能团队,而后者则代表一种跨职能(Cross Function)的团队构建方式,团队中包括服务端、前端等各种角色。上下文的构建以及界限的划分是一项跨职能的活动,如果团队组织架构具备跨职能特性,可以安排特定的团队负责特定的上下文并统一管理该上下文对应的界限。

上下文集成技术

明确各个上下文的边界之后,我们同时需要考虑的问题就是如何对拆分后的功能进行组装。对于这个问题,基本的思路就是系统集成。站在高层次的架构分析角度,任何一个系统都可以处在其他系统的上游(Upstream),也可以位于其他系统的下游(Downstream)。所以系统集成实际上就是将上游系统与下游系统进行整合共同完成某一项业务的过程。如下图所示,假设通过领域拆分得到三个上下文 A、B 和 C,A 上下文同时位于 B、C 上下文的上游,B 上下文相对 A 而言处于下游但相对 C 而言处于上游,C 上下文则处在整个系统的最下游。

上下文集成的基本思路在于解耦和统一。对于解耦而言,一方面在于技术实现上的依赖性,需要支持异构系统的有效交互;一方面也需要把关注于集成的实现与业务逻辑的实现相分离,确保集成机制的独立性。而统一的含义在于一致性,即上游系统应该定义协议,让所有下游系统通过协议访问,确保在数据传输接口和语义上各个上下文之间能够达成一致。

防腐层与统一协议

针对以上两种思路,我们可以分别抽象出两种最基本的集成模式,即防腐层(AntiCorruption Layer,ACL)和统一协议(Unified Protocol,UP)。防腐层强调下游系统根据领域模型创建单独一层,该层完成与上游系统之间的交互,从而隔离业务逻辑,实现解耦。统一协议则是提供一致的协议定义,促使其他系统通过协议访问。显然,防腐层模式面向下游系统而统一协议面向上游系统。

在对任何子域和上下文进行提取时,确保从组织关系和集成模式上对上下文集成进行抽象。下图就是上下文关系在集成方案上的一种表现形式。

领域事件

现实中很多场景都可以抽象成事件,如当……发生……时、如果发生……、当……时通知我等,领域事件(Domain Event)指的就是把领域中所发生的活动建模成一系列离散事件。领域事件也是一种领域对象,是领域模型的组成部分,同时也为上下文之间的交互提供了另一种方式。

领域事件生命周期包括产生、存储、分发和使用四个阶段,根据角色的不同,事件的产生处于事件发布阶段,而存储、分发和使用可以归为事件的处理阶段。但针对某种特定事件并不一定都会经历完整的生命周期。

事件的识别有时候具有一定的隐秘性,当一个实体依赖于另外一个实体,但两者之间并不希望产生强耦合而又需要保证两者之间的一致性时,我们通常就可以提取事件,这是事件最容易识别的场景。如在移动医疗系统的挂号(Registration)场景,为了避免挂号系统与其他系统之间产生强耦合,当一个挂号结束时,该上下文更新挂号结果信息并通知相关兴趣方,这个过程中我们就可以提取 RegistrationFinished 事件。

领域事件同样需要建模,一般使用过去时对事件进行命名,如上述的 RegistrationFinished 事件。领域事件包含唯一标识、产生时间、事件来源等元数据,也可以根据需要包含任何业务数据。同时,领域事件具有严格意义上的不变性,任何场合都不可能对事件本身做任何修改,因为事件代表的是一种瞬时状态。

事件驱动架构的发布-订阅机制非常适合与其他风格进行整合构成复合型架构风格,最典型的就是与管道-过滤器(Pipe-Filter)风格进行整合。管道中流转的数据就是领域事件,而过滤器可以是一个子域中的某个组件,也可以是进行跨子域的界限上下文。下图就是一个典型的管道-过滤器示例,我们看到事件发布器 UserPasswordPublisher 发布了一个 UserPasswordChanged 事件,而订阅该事件的 UserPasswordHandler 组件对该事件进行处理之后再次发送一个 UserPasswordChangeSucceed 事件,负责接收 UserPasswordChangeSucceed 事件的 UserPasswordChangeSucceedHandler 组件可以根据需要对该事件进行后续处理。整个过程中能够发送事件的组件实际上就是管道,而处理事件的组件就是过滤器,通过管道和过滤器的组合形成基于领域事件的管道-过滤器风格。

领域驱动的架构风格

作为高层次的设计维度,面向领域的策略设计同样涉及系统的体系架构,领域驱动设计在设计思想上有其独特的考虑。

领域驱动设计核心组件

设计架构分层的前提是明确系统的核心组件,分层体现的就是对这些核心组件的层次和调用关系的梳理。在领域驱动设计中,一般认为存在以下四大组件:

领域组件。代表对整个领域驱动设计的核心,包含对领域、子域、界限上下文等策略设计相关内容,也包含后续所要阐述的所有技术设计组件。领域组件代表抽象模型,并不包含具体实现细节和技术。

基础设施(Infrastructure)组件。这里的基础设施组件范围比较广泛,即可以包括通用的工具类服务,也包括数据持久化等具体的技术实现方式。领域组件中的部分抽象接口需要通过基础设施提供的服务得以实现,所以基础设施组件对领域组件存在依赖关系。

应用组件(Application)。应用组件面向用户接口组件,是系统对领域组件的一种简单封装,通常作为一种门户(Facade)或网关(Gateway)对外提供统一访问入口,在用户接口和领域之间起到衔接作用。同时,因为基础设施组件是对领域组件部分抽象接口的具体实现,所以应用组件也会使用基础设施组件的服务完成具体操作。

用户接口(User Interface)组件。用户接口处于系统的顶层,直接面向前端应用,调用应用组件提供的应用级别入口完成用户操作。

在领域组件中,为了建立完整的领域模型,势必会涉及到数据的管理。数据相关操作对于领域模型而言只是持久化的一种抽象,不应该关联具体的实现方式。比如,我们可以用关系型数据库去实现某个数据操作,有时候根据需要我们同样可以采用各种 NoSQL 技术。显然,无论是关系型数据库操作还是 NoSQL 技术都不应该包含在领域组件中,通常我们会使用接口的方式抽象数据访问操作,然后通过依赖注入方法把实现这些数据访问接口的组件注入到领域模型中,这些数据访问的实现我们就可以统一放在基础设施组件中,也就是说基础设施组件实现了领域组件中的抽象接口。

通过以上分析,我们可以把领域驱动设计中的四种组件分别列为四层,并梳理各个层次之间的关系形成分层结构图(见下图),该图的表现形式与上述各个组件描述是一致的。

传统的分层结构根据是否可以跨层调用可以归为两类,即严格分层架构和松散分层架构,前者认为各个层次之间不允许存在跨层调用的方式,而后者并不对此有严格限制。所以,领域驱动设计分层结构实际上是一种松散分层架构,位于系统流程上游的用户接口层和应用层,以及位于系统流程下游的的具备数据访问功能的基础设施层都依赖于抽象层,事实上已不存在严格意义上的分层概念。领域驱动设计思想认为应该推平分层架构,不使用严格的分层架构来构建系统,平面形架构(见下图)也就应运而生。

在上图中,平面形架构促使我们转换视角重新审视一个系统,划分内部和外部成为架构搭建的切入点,系统由内而外围绕领域组件展开,领域组件位于平面形架构的最内层,应用程序也可以包含业务逻辑,与领域组件构成系统的内部基础架构;而对于外部组件而言,通过各种适配器进行上下文集成,这些适配器包括数据持久化,也包括面向第三方的数据集成。基于依赖注入和 Mock 机制,适配器组件可以进行方便的模拟和替换。

下篇导读

本篇主要讨论的面向领域的设计思想以及面向领域的策略设计,下一篇我们将继续深入分析领域驱动设计并重点介绍面向领域的技术设计。

上一篇
下一篇
目录