程序设计——领域驱动设计

程序设计的所有原则和方法论都是追求一件事——简单——功能简单、依赖简单、修改简单、理解简单。因为只有简单才好用,简单才好维护。因此,不应该以评论艺术品的眼光来评价程序设计是否优秀,程序设计的艺术不在于有多复杂多深沉,而在于能用多简单的方式实现多复杂的业务需求;不在于只有少数人能理解,而在于能让更多人理解。

系列文章

程序设计——方法论总结
程序设计——领域驱动设计

1 问题

1.1 软件设计方法有哪些

在软件设计中,需要综合运用上面的方法,领域驱动设计作为一个设计指导思想,亦是如此。它的核心是模型驱动设计,即在面向对象的基础上构建领域模型来驱动软件设计。领域模型需要使用领域内通用语言来获得,使用分治思想高内聚低耦合原则把一个大领域问题拆为多个具有自治能力的子领域,根据领域存在的价值又分为核心域、通用域、支撑域。

使用界限上下文维护领域模型的上下文语境,使用上下文映射来管理各个界限上下文之间的协作方式。

在界限上下文内部进行模型驱动设计,使用分层思想把界限上下文内部表达领域业务知识和技术知识的部分分离出来独立处理,其中领域业务知识是整个设计过程的重心,整个系统也因此而存在。

1.2 领域模型是什么,用什么工具来呈现领域模型?

领域模型: 用于表达领域业务规则和流程,由各种组件及组件间关系组成。在设计图上体现为各种框图及框图关系,在代码上体现为一组具有相关性的类。
在领域模型中表示组件的有:

  1. 封装领域逻辑的组件: 实体(entity)、值对象(value object)、领域服务(domain service)和领域事件(domain event)、Specification(规格)。
  2. 维护领域边界的组件:
    1. 聚合(aggregate): 聚合是一种边界,它可以封装一到多个实体与值对象,并维持该边界范围之内的业务完整性
  3. 管理对象生命周期的组件:
    1. 工厂(factory): 工厂负责聚合的创建,用于封装复杂或者可能变化的创建逻辑。
    2. 资源库(repository): 资源库负责从存放资源的位置(数据库、内存或者其他Web资源)获取、添加、删除或者修改聚合。在领域模型中,只定义资源库接口,实现交由其它层去实现。
  4. 与外部界限上下文交互的组件:
    1. Client或者Adaptor: 表示调用外部界限上下文提供的服务的客户端或适配器。
    2. Publisher: 表示事件消息发布器。

1.3 一个优秀的领域模型应该具有怎样的特征?

  1. 正确性: 仅包含与解决给定领域问题相关的信息,命名上正确使用领域通用语言作表达
  2. 完整性: 领域的规则和行为只存在模型内,模型外不应该有领域知识散落、甚至默认行为。比如查询某个业务配置是否启用,这个启用状态只能由领域传给基础设施层,而不能在基础设施层直接指定status等于启用状态对应的枚举值。因为基础设施层不需要关心要查什么类型的业务数据,它只需要按照领域层传的条件做就行了。
  3. 自治性: 自行管理模型内部的依赖关系,数据关系
  4. 独立性: 一个领域模型独立于其它模型而存在,逻辑行为独立、数据模型独立。即使数据来自外部,但数据模型应该是自己的。
    在DDD中,不只是领域模型需要这些特性,大到子领域、界限上下文、分层需要,小到聚合、实体也需要。

1.4 如何创建领域模型?

按分层思想设计领域上下文内部结构,把领域业务和技术实现分离出来,如洋葱架构、六边形架构、菱形架构,领域建模设计属于领域层内容。领域建模分为分析、设计、实现三个阶段,在这三个阶段中,除了与领域相关的讨论,不需要考虑外部交互,包括如何对外提供服务、如何使用外部服务;也不需要考虑数据如何存储、使用怎样的存储介质。

1.4.1 分析阶段

  1. 分清领域是什么,领域的边界在哪里
  2. 统一领域内术语、概念,组建领域的通用语言
  3. 明确要解决的领域问题
  4. 理清领域的业务规则
  5. 领域太大,遵循高内聚低耦合的原则进行子领域拆分,再针对子领域进行分析建模

产出:通用语言、概念结构图、用例图、业务流程图、总体架构图(包含界限上下文、上下文映射、核心域、通用域、支撑域)

如下图是确定了界限上下文的领域分析模型,以“为薪资管理系统”例。

1.4.2 设计阶段

  1. 使用面向对象思想,设计领域数据模型(值模型、实体模型)、领域业务逻辑模型、模型关系
  2. 模型聚合:把业务不可拆分的模型聚合在一块,作为一个整体。
  3. 领域服务:Service
  4. 模型创建:Factory
  5. 数据存取接口抽象:repository
  6. 对外获取数据、传递数据接口:adaptor、transfer
  7. 异常往外抛出

产出:类图、对象图、序列图、活动图、状态图
以“薪资管理系统”例,下图是设计阶段建模过程。设计阶段比较重要的就是聚合设计,因为聚合控制了系统内组件的耦合性。

1. 确定实体和值对象,下图黄色为值对象 2. 分解关系薄弱点划分聚合,如下图
3. 服务驱动设计,如下图是确定支付日期的序列图 4. 按领域模型进行设计数据模型

1.4.3 模型实现

  1. 根据建模设计内容进行代码编写
  2. 代码命名需与通用语言一致
  3. 异常不捕获,往上层抛

1.5 怎么使用领域模型来驱动设计?

在领域模型创建后,系统设计和开发阶段都需要基于模型完善系统的技术设计,包括:

  1. 基础设施层设计
    1. 实现领域层的Repository接口,保存领域模型数据:与数据库的交互,使用ORM框架即可。防腐上然需做好领域模型与数据库模型的转换。
    2. 实现领域层的Adaptor接口,调用三方服务:与外部系统的交互之间添加防腐层,对消息进行转换,把内部对象转化为请求对象,把外部响应对象转化为内部对象。
    3. 事件消息发布:像MQ中发布消息
    4. 数据缓存:缓存数据的增删改查、失效
    5. 异常不捕获,往上抛
  2. 应用层设计
    1. 面向服务设计,组合各领域服务对上游提供服务。
    2. 应用层应以最小原则设计接口,即数据传输尽可能少,领域服务需要的数据在应用层内做数据转换。
    3. 异常不捕获,往上抛
  3. 服务协议层
    1. Http rest
    2. RPC服务
    3. 捕获内部异常,封装异常消息

因此在整个过程以创建领域模型为中心,再基于领域模型对界限上下文中其它设施层进行交互设计,换一个说法就是面向服务设计。

1.6 领域模型驱动设计与MVC+三层架构相比较如何?

MVC+三层架构领域模型驱动设计
学习难度低,理解上相对比较简单高,需要具备一定的开发经验
使用难度低,业务知识和技术支持(如MQ、RPC调用)通通放service层,有时Dao层的接口也可以有业务含义。高,
1. 难度在于把看似分散的事物抽象成一个领域模型,需要花比较多的时间运用合适的技术手段进行领域分析、模型设计,领域模型质量决定了系统的可控度。
2. 代码的归属也相对比较严格,增加了设计成本,比如领域知识不能遗漏在领域层外,因此DAO和应用层接口不能有领域业务含义。
3. 需要写比较多的转换代码,比如应用层需要把客户请求对象转化为领域对象传给领域层。基础设施层需要把领域对象转化为数据库对象保存到数据库。
维护难度高,随系统复杂度提升,难度越大。因为代码分散,聚合度不高,很容易变得难以修改。低。
1. 结构清晰:代码归属比较严格,技术代码与业务代码分离
2. 职责清晰:每个类文件有严格的职责,各司其职。
3. 高内聚低耦合:业务代码只在领域层内,比较容易定位。
4. 更能适应业务的变化:小到更改某个子模型代码,大到替换整个模型,皆可比较容易的做到过度。
适用场景1. 小规模业务系统
2. 简单业务系统
1. 复杂业务系统
2. 大型团队,代码归属比较严格,因此有比较清晰的结构,团队之间项目交接比较容易。

领域驱动设计并非“银弹”,它的适用范围主要是大规模的、具有复杂业务的中大型软件系统,至于对技术复杂度的应对,它的选择是划清边界相互“隔离”,然后交给专门的技术团队设计合理的解决方案。

2. 应用场景

用于应对复杂系统的设计,并且在系统的维护中也应该遵守该模式。
其中复杂的定义:

  1. 规模复杂:
    1. 业务规模:业务多、业务关系多而复杂
    2. 数据规模:数据多、数据关系多而复杂
  2. 结构复杂
    1. 系统架构
    2. 组织架构
  3. 更新快:业务变化、数据变化

应对方式:维护好通用语言、分层设计、领域拆分、防腐设计、不断迭代

3. 核心概念

DDD中所述的很多元模式概念并不是DDD所特有的,在DDD中给出了如何使用这些元模式来表达领域模型。

3.1 系统精炼

DDD一直强调领域划分,划分领域是边界思维的一种运用场景,下面是与领域划分相关的行为。

概念描述
通用语言(Ubiquitous Language)在业务需求讨论阶段获得,比如领域术语、领域规则、领域行为
分层设计1. 对业务结构进行分层
2. 对系统结构进行分层
3. 对界限上下文内部进行分层
4. 对领域模型进行分层
界限上下文(领域)限界上下文划定了领域(业务)知识的边界,不同的限界上下文需要不同的领域知识,形成了各自的知识语境
子领域——核心领域核心领域体现了目标系统的核心价值,具有不可替代作用
子领域——通用领域通用领域不具有个性特征,在各业务领域都能用到,又不可或缺,比如权限系统。
子领域——支撑领域支撑领域为一些核心领域的业务服务提供辅助作用,但又不具有通用性,比如地图功能。

3.2 上下文映射

上下文映射是DDD中的概念,它呈现了领域与领域之间如何交互和代码如何共享、团队与团队之间如何协作。

应用间交互

概念描述
开放主机服务定义公开服务的协议,包括通信的方式、传递消息的格式。比如REST HTTP、RPC
发布/订阅由消息队列担任事件总线发布与订阅事件。一个界限上下文往消息队列发布消息,另一个界限上下文从消息队列订阅消息。

团队间协作

概念描述
分离方式两个限界上下文之间没有一丁点儿的关系
合作模式两个限界上下文的团队在接口的演化上进行合作以同时满足两个系统的需求。
供应方/客户方当一个界限上下文单向地为另一个限界上下文提供服务时,它们对应的团队就形成了客户方/供应方(customer/supplier)关系。这种关系中供应方应客户方需求而提供服务实现。
遵奉者服务供应方决定服务的定义和实现,客户方只能遵守服务方的定义使用服务。
发布方/订阅方由消息队列担任事件总线发布与订阅事件。一个界限上下文往消息队列发布消息,另一个界限上下文从消息队列订阅消息。这两个界限上下文对应的团队就形成了发布方/订阅方关系

代码开发

概念描述
共享内核将那些稳定且具有复用价值的领域模型对象封装到共享内核上下文中,其它领域可以使用内核中的模型。
发布语言发布语言(published language)是一种公共语言,用于两个限界上下文之间的模型转换,即一个SDK。
防腐层应用于依赖第三方服务的情形,为了避免三方服务变化对内部领域模型的影响,需要添加一个防腐层。防腐层的实现可以是一个adaptor用于适配三方服务,结合convertor实现内部模型和三方消息的转换。

3.3 软件构造块

构造块是代码中各代码角色的一个抽象名称。它们不是DDD特有的,只是DDD规范了这些角色的职责。

概念描述
Entity Object以一个具有身份标识的主体类型来表达领域逻辑中具有个性特征的概念,而这个主体的状态在相当长一段时间内会持续地变化。实体除了标识和属性外,还具有领域行为(更改状态、自给自足、与其它实体相互协作)
Value Object值对象只关心属性,无需身份标识,具有自给自足的领域行为。值对象应该设计为不可变。
Service封装领域行为
领域服务并不映射真实世界的领域概念(名词),而单纯地体现一种领域行为(动词)
为了缩小领域服务的粒度,可以在服务类中添加一个动词做修饰,如UserLoginDomainService
Repostory资源库(repository)是对数据访问的一种业务抽象,一个聚合一个Repostory,管理聚合中领域模型对象的生命周期。
Factory工厂负责聚合的创建,用于封装复杂或者可能变化的创建逻辑
Specification规格就是一个谓词,可用来确定对象是否满足某些标准,代码上用于逻辑判断
Aggregate将实体和值对象划分为聚合并围绕着聚合定义边界。从边界内的类关系树中选择根实体作为聚合的根,外部对象仅能持有聚合根的引用。在聚合内部维护领域业务的完整性一致性。有根对外提供领域行为方法,实现内部各对象之间的协作。
在设计中聚合边界体现为一个线条,这个线条围住的内容组合为一个聚合。
在实现中聚合体现为由一个包管理的一组类文件。
如下图,左侧的聚合结构图体现了以AggregateRoot为根的对象树,右侧的行为序列图则通过聚合根向外暴露整体的领域行为,内部由聚合边界内的实体和值对象共同协作。聚合的边界体现了聚合的控制能力。

3.4 其它

领域职责分层:作业层、能力层、决策支撑层、潜能层、策略层
设计模式运用:
抽象核心领域、分离核心领域、可插入式组件框架…

4. 架构演进

4.1 经典分层架构

分层架构是运用最为广泛的架构模式,几乎每个软件系统都需要通过层(Layer)来隔离不同的关注点(Concern Point),以此应对不同需求的变化,使得这种变化可以独立进行。

  • 用户界面层:负责向用户展现信息和解释用户命令。包含web端UI界面、移动端UI界面、第三方服务等。
  • 应用层:很薄的一层,用来协调应用的活动,它不包含业务逻辑,它不保留业务对象的状态。在领域设计中,它其实是一个外观(Facade),一般是供其他界限上下文基础设置层中controller调用。
  • 领域层:本层包含领域的信息,是业务软件的核心所在。在这里保留业务对象的状态,对业务对象状态的持久化被委托给基础设置层。它包含领域设计中的:聚合、值对象、实体、服务、资源接口等。
  • 基础设置层:不要简单的理解为物理上的一层,它作为其他层的支撑库而存在。它提供了层间的通讯,实现对业务对象的持久化。一般包含:网络通讯、资源实现(数据库持久化实现)、异步消息服务、网关、controller控制等。

4.2 各层依赖和调用关系

领域层在整个工程的依赖关系中是独立的,即它不依赖于任何一层。业务开发时先开发领域逻辑,再开发应用层和基础设施层。

4.3 六边形架构

六边形架构又称“端口和适配器模式”,是Alistair Cockburn在2005年提出的一种具有对称性特征的架构风格。在这种架构中,系统通过适配器的方式与外部交互,将应用服务于领域服务封装在系统内部。

六边形架构还是一种分层架构,如上图所示,它被分为了三层:端口适配器、应用层与领域层。而端口又可以分为输入端口和输出端口。

  • 输入端口
    用于系统提供服务时暴露API接口,接受外部客户系统的输入,并将客户系统的输入转化为程序内部所能理解的输入。系统作为服务提供者对外的接入层可以看成是输入端口。
  • 输出端口
    为系统获取外部服务提供支持,如获取持久化状态、对结果进行持久化,或者发布领域状态的变更通知(如领域事件)。系统作为服务的消费者获取服务是对外的接口(数据库、缓存、消息队列、RPC调用)等都可以看成是输入端口。
  • 应用层
    定义系统可以完成的工作,很薄的一层。它并不处理业务逻辑通过协调领域对象或领域服务完成业务逻辑,并通过输入端口输出结果。也可以在这一层进行事务管理。
  • 领域层
    负责表示业务概念、规则与状态,属于业务的核心。
    应用层与领域层的不变性可以保证核心领域不受外部的干扰,而端口的可替换性可以很方便地对接不用的外部系统。

4.4 洋葱架构

2008年Jeffrey Palermo提出了具有分层思想的洋葱架构,如下图,同心圆代表软件的不同部分,从里向外依次是领域模型,领域服务,应用服务和外层的基础设施和用户终端。

洋葱架构根据依赖原则,定义了各层的依赖关系,越往里依赖程度越低,代码级别越高,越是核心能力。外圆代码依赖只能指向内圆,内圆不需要知道外圆的情况,这种架构也是典型的分层架构,和DDD分层架构一样,都体现了高内聚,低耦合的设计特性。洋葱架构也常作为指导微服务设计的重要架构之一。

4.5 菱形对称架构

张逸提出菱形对称架构,其上下文的组成:

  • 北向网关的远程网关:进程间通信
  • 北向网关的本地网关:进程内通信
  • 领域层的领域模型:领域逻辑
  • 南向网关的端口抽象:各种资源库操作的抽象借接口,可以被领域层依赖,
  • 南向网关的适配器实现:网关端口的实现,运行时通过依赖注入将适配器实现注入到领域层

菱形对称架构去掉了应用层和基础设施层的概念,以统一的网关层进行概括,并以北向与南向分别体现了来自不同方向的请求。如此形成的对称结构突出了领域模型的核心作用,更加清晰地体现了业务逻辑、技术功能与外部环境之间的边界。

资源库视为防腐层的端口与适配器,作为领域建模时的角色构造型,与场景驱动设计更好地结合,增强了领域模型的稳定性。应用层被去掉之后,被弱化为开放主机服务层的本地服务,相当于从设计层面回归到服务外观的本质,也有助于解决应用服务与领域服务之间的概念之争。

为了避免内部领域模型的泄露,北向网关的服务契约不能直接暴露领域模型对象,需要为组成契约的方法参数和返回值定义专门的模型。该模型主要用于调用者的请求和响应,因而称为“消息契约模型”,南向网关亦是如此。

菱形对称架构比较符合程序员自上而下的分层思维,容易被理解,比如一次调用的运行过程:从北向网关的远程服务层 -> 应用服务层 -> 领域层 -> 南向网关端口层 -> 适配器层持久化到数据库。代码模型如下:

YAML
currentcontext
   - ohs(northbound)
      - remote
         - controller
         - resource
         - provider
         - subscriber
      - local
         - appservice
      - pl(message)
   - domain
   - acl(southbound)
      - port
         - repository
         - client
         - publisher
      - adapter
         - repository
         - client
         - publisher
      - pl(message)

5. 参考书籍

  1. 《领域驱动设计 软件核心复杂性应对之道》作者:埃里克·埃文斯(Eric Evans),“领域驱动设计之父”经典著作。纸质书购买地址https://item.jd.com/12870065.html
  2. 《解析领域驱动设计》作者:张逸,微信读书链接https://weread.qq.com/web/bookDetail/4fc328a0729350754fc56d4
  • 26
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值