DDD
DDD发展情况
- 2003年《领域驱动设计》Eric Evans正式提出 DDD 《领域驱动设计精简版》
- 2013年《实现领域驱动设计》Vaughn Vernon 发展领域事件、六边形架构战略设计
- 2013年 Alberto Brandolini 事件风暴建模法
DDD相关概念
模型:对领域的抽象和模拟
建模:针对特定问题(超市、电商)建立领域的合理模型
软件系统复杂来源:领域 -(建模)-> 模型 -(设计+技术)-> 代码
- 业务本身的复杂性:
钱:币种 + 数量
人民币:编号 + 面额 + 版本
钞票:编号 + 面额 + 版本 + 形状 + 颜色 + 图案 + 重量 + 防伪标签 - 设计实现引入额外复杂性:
DDD核心思想
- 模型分解
- 领域划分 Subdomain
- 限界上下文 Bounded Context
- 模型驱动设计(Model Driven Design)
通过分层架构隔离领域层、仔细选择模型和设计方案等措施保持实现与模型的一致
DDD易混淆点
DDD与面向对象
面向对象:
- OOAD:object oriented analysis and design,面向对象分析与设计(与DDD同一层)
- OOP:object oriented programing,面向对象编程(为DDD所依赖)
DDD与OOAD联系和区别:
- 联系:
- 都是建模和设计的思想
- 部分建模方法和工具可复用
- 区别:
- OOAD 没有战略设计;DDD 通过战略设计划分领域和模型
- OOAD 仅用对象描述世界;DDD 的描述更细致
DDD与敏捷开发
- 联系:
- 都是软件工程领域的思想,解决软件工程中的不同问题,一般可结合应用
- 区别:
- 敏捷:关注流程和文化;DDD:关注建模设计方法
- 敏捷:重人员轻文档;DDD:重视统一语言的建立
DDD战略设计
用户故事
描述模型
- 问题空间的描绘:who + what + why,As a role, I want to action (so thst benefit)
- 文字表述
- 讨论+图形表达
通用语言
- 在讨论模型和定义模型时,团队使用的同一种语言(商品-sku 订单-order 支付-payment)
- 领域知识需要在团队内部高效流转,模型需要描述
- 通用语言要体现在代码里
战略设计
DDD中对问题空间和解决方案空间进行分解的过程,目的是分解模型以控制复杂性
领域划分
进行分工协作而非基于需求,以分离关注点为原则对问题空间的划分
- 子域:领域中某个方面的问题和解决它所涉及的一切
- 核心域:交易域 + 运营域
- 通用子域:商品域 + 支付域
- 支持子域:用户域
- 精炼:将子域再细化为核心域、通用子域、支持子域
限界上下文(BC)
划分限界上下文
-
Domain Storytelling 领域故事陈述法
- 边界特征
- 单向联系
- 语义区别
- 活动触发方式不一样
- 边界特征
-
Event Storming 时间风暴法
-
基于子域概念提取
确定上下文映射
上下文映射:指限界上下文之间的模型映射关系。即上下文间的协作。描述团队之间的协作关系以及上下文之间的集成关系。如:交易上下文=商品+订单+支付+物流
上下文映射模式
- 开放主机服务:服务提供方为所有消费方提供一套公共的API,针对通用的功能和模型
- 微信支付上下文(UpStream) --> 商城支付上下文(DownStream)
- 顺从者:没有模型到模型的转换,一个上下文沿用另一个上下文的部分模型
- 大泥球:由混杂的模型构成的糟糕系统,模型不稳定且难于维护
- 微信支付上下文(UpStream) --> 商城支付(DownStream)<-- 支付宝支付上下文(UpStream)
- 与大泥球合作的上下文要确保自身不被污染,设置防腐层
- 防腐层:
- 把上游上下文的模型转换成自己上下文的模型
- 是下游上下文中访问外部模型的一个代理层
- 共享内核
- 两个上下文共享部分模型
- 包括但不限于代码、jar包、.so、数据库表等
- 慎用,仅当团队紧密合作且共享部分稳定
- 合伙人
- 技术无关,是一种团队协作关系
- 两个团队之间可以随时互通有无,协同变更
- 客户/供应商
- 下游上下文可以向上游上下文提需求
- 一般用于核心域与非核心域之间的协作
- 分道扬镳
- 两个上下文无协作,各自独立
- 当两个上下文之间的集成成本过高
- 公开语言
- 标准化与协议化的模型
- 所有上下文都可以与公开语言中的模型进行转换
- 对接了公开语言的上下文之间可以实现组件化对接
战术设计
- 对各个BC的细节设计过程
- BC内部的模型结构与完整技术方案
领域分层
传统DDD分层模型
- 接口层 Interface
- 应用层 Application
- 基础设施层 Infrastructure
- 领域层 Domain
洋葱分层模型:
- 适配层 Adapter
- 应用层 Application
- 基础设施层 Infrastructure
- 领域层 Domain
领域事件
领域事件:领域中发生的任何领域专家(运营人员、管理员、用户)感兴趣的事情,领域事件一般由聚合产生
领域事件基本属性
- 事件命名:OrderCancelEvent
- 事件ID
- 产生时间
发布和订阅方式
- 外部系统:API定向通知、API定时拉取、消息队列
- 内部系统:观察者模式、数据库流水、消息队列
领域事件的存储
- 直接使用消息中间件的存储:冗余机制 + 做好备份
- 基于数据库:分布式 + 按时间分区
事件处理的要求
- 顺序性:聚合ID + 存储分片 + 消费分组
- 幂等性:用幂等性代替分布式事务,状态判断或去重
建模方法
- Domain Storytelling(领域故事陈述法)
- Event Storming(事件风暴法)
- 4C(四色建模法)
分层领域模型
分层领域模型简介
开发中,我们习惯项目或系统横切为表现层-业务逻辑层-数据访问层
优点:
- 解耦:系统越大、需求变化快,就越需要保证程序间的依赖关系越少,分层使我们更容易应对
- 降低维护成本和升级成本
- 代码或逻辑复用
阿里开发手册中,建议使用的应用分层
- 终端显示:各个端的模板渲染并执行显示,如前端 JS 渲染,移动端展示
- 开放接口:
- 可直接封装 Service 方法暴露成 RPC 接口
- 通过 Web 封装成 HTTP 接口
- 进行网关安全控制/流量控制等
- Web:主要对访问控制进行转发,各类基本参数校验,或不复用的业务简单处理
- Service:相对具体的业务逻辑处理
- Manager:通用业务处理层
- 对第三方平台封装的层,预处理返回结果及转化异常信息
- 对 Service 层通用能力的下沉,如缓存方案、中间件通用处理
- 与 DAO 层交互,对多个 DAO 的组合复用
- DAO:数据访问层,与 MySQL、Oracle、Hbase 等进行数据交互
- 外部接口或第三方平台:包括其它部门 RPC 开放接口,基础平台,其它公司的 HTTP 接口
细分层级仅是为了满足业务需要。千万不要为了分层而分层。过多的层会增加系统的复杂度和开发难度
经典分层领域模型
- DO:Data Object,与数据库表结构对应,通过 DAO 层想上传输数据源对象
- DTO:Data Transfer Object,数据传输对象,Service 或 Manager 向外传输的对象
- BO:Business Object,业务对象,由 Service 层输出的封装业务逻辑的对象
- AO:Application Object,应用对象,在 Web 层与 Service 层之间抽象复用对象模型
- VO:View Object,视图层对象,通常是 Web 向模版渲染引擎层传输的对象
- Query:数据查询对象,各层接收上层的查询请求。注意超过 2 个参数查询封装,禁止使用 Map 类传输
BO、AO、DTO的界限不是非常明确。这也是因为系统处理的业务不同、复杂度不同导致的。所以在设计系统分层和建模的时候,需要综合考虑实际应用场景
根据业务和习惯使用不同的 Bean:
- POJO:Plain Ordinary Java Object,简单 Java 对象,提供对应的 getter/setter
- JavaBean,可序列化(持久化)的 POJO
具有空构造函数
所有属性为私有属性,,不应该有公共属性,提供 getter/setter 向外提供访问和操作私有属性的方法
实现序列化接口:java.io.Serializable
- PO:Persistent Object,持久化对象,主要用于持久化层,与数据库对应,通常也是 ORM 中的实体,如,使用 JPA 时的 Entity 与数据库表做映射,通常是一个 JavaBean,和阿里规定的 DO 相同作用
- DAO:Data Access Object,数据访问对象,与数据库交互的对象,提供不同的接口访问数据库来实现对数据库的操作,而接口使用的数据交互通常是 PO 或 DO,通过它可使用面向对象的方式来与数据库交互
对象含义 | 层级 | 业内命名 | 阿里规约 | 个人习惯 |
---|---|---|---|---|
视图层对象 | 视图层Adapter | VO | VO | VO |
数据传输对象 | 应用层Application | DTO | DTO | DTO |
数据存储对象 | 基础设施层Infrastructure | PO(Persistent Object) | DO(Data Object) | DO |
领域对象 | 领域层Domain | DO(Domain Object) | BO(Business Object) | 领域通用名 |
分层领域模型转换
考虑点:
- 原对象和目标对象相同属性类型或名称不一样
- 集合类属性中的泛型不一样
- 能不能只复制部分属性
- 能不能自定义转换逻辑
- 嵌套对象是深拷贝还是浅拷贝