DDD
目录
基本概念
- aggregate (聚合):聚合就是一组相关对象的集合,我们把聚合作为数据修改的单元。外部对象只能引用聚合中的一个成员,我们把它称为根。在聚合的边界之内应用一组一致的规则。
- bounded context(限界上下文):特定模型的限界应用。限界上下文使团队所有成员能够明确地知道什么必须保持一致,什么必须独立开发。
- context(上下文):一个单词或句子出现的环境,它决定了其含义。参见 bounded context 。
- context map(上下文图):项目所涉及的限界上下文以及它们与模型之间的关系的一种表示。
- core domain(核心领域):模型的独特部分,是用户的核心目标,它使得应用程序与众不同并且有价值。
- deep model(深层模型):领域专家们最关心的问题以及与这些问题最相关的知识的清晰表示。深层模型不停留在领域的表层和粗浅的理解上。
- distillation(精炼):精炼是把一堆混杂在一起的组件分开的过程,从中提取出最重要的内容,使得它更有价值,也更有用。在软件设计中,精炼就是对模型中的关键方面进行抽象,或者是对大系统进行划分,从而把核心领域提取出来。
- 领域(domain):知识、影响或活动的范围。
- 领域专家(domain expert):软件项目的成员之一,精通的是软件的应用领域而不是软件开发。并非软件的任何使用者都是领域专家,领域专家需要具备深厚的专业知识。
- domain layer(领域层):在分层架构中负责领域逻辑的那部分设计和实现。领域层是在软件中用来表示领域模型的地方。
- entity(实体):一种对象,它不是由属性来定义的,而是通过一连串的连续事件和标识定义的。
- factory(工厂):一种封装机制,把复杂的创建逻辑封装起来,并为客户抽象出所创建的对象的类型。
- implicit concept(隐式概念):一种为了理解模型和设计的意义而必不可少的概念,但它从未被提及。
- layered architecture(分层架构):一种用于分离软件系统关注点的技术,它把领域层与其他层分开。
- model(模型):一个抽象的系统,描述了领域的所选方面,可用于解决与该领域有关的问题。
- model driven design (模型驱动的设计):软件元素的某个子集严格对应于模型的元素。也代表一种合作开发模型和实现以便互相保持一致的过程。
- repository (存储库):一种把存储、检索和搜索行为封装起来的机制,它类似于一个对象集合。
- service (服务):一种作为接口提供的操作,它在模型中是独立的,没有封装的状态。
- strategic design(战略设计):一种针对系统整体的建模和设计决策。这样的决策影响整个项目,而且必须由团队来制定。
- ubiquitous language (通用语言):围绕领域模型建立的一种语言,团队所有成员都使用这种语言把团队的所有活动与软件联系起来。
- value object (值对象):一种描述了某种特征或属性但没有概念标识的对象。
领域建模
- 模型是对领域的抽象,建模是针对特定问题建立领域的合理模型
- 领域 -> 模型(领域知识/业务需求) -> 代码(技术实现)
- 模型分解(领域划分/限界上下文)解决面向对象的复杂性(一个对象太臃肿)
- 复杂性来源于业务本身的复杂性和技术引入的额外复杂性
- 领域驱动设计通过分解模型和模型驱动设计控制复杂性
有效建模的要素
- 模型和实现的绑定。
- 建立了一种基于模型的通用语言。
- 开发一个蕴含丰富知识的模型。
- 提炼模型。
- 头脑风暴和实验。
领域建模容易产生问题的应对方法
- 不要单纯以角色的行为(活动)为中心进行沟通和建模
- 领域沟通过程中,研发人员发挥主动性,以便发现深层模型
- 场景走查
常用建模方法
DDD 与其他开发方法的区别
DDD 与面向对象 OOAD 的区别
区别 | 联系 |
---|---|
OOAD 没有战略设计,DDD 通过战略设计划分领域和模型 | 都是建模和设计方法 |
OOAD 仅用对象描述世界,DDD 的描述更细致(实体/值对象/聚合/聚合根) | 部分建模方法和工具可复用 |
DDD 与敏捷
区别 | 联系 |
---|---|
敏捷:关注流程和文化,DDD:关注建模和设计方法 敏捷:重人员轻文档,DDD:重视统一语言的建立 |
都是软件工程领域的思想,解决软件工程中的不同问题,一般可结合应用 |
战略战术设计
建模和设计的整体流程:挖掘用户故事 -> 建立通用语言 -> 战略设计 -> 战术设计
战略设计
- DDD 中对问题空间和解决方案空间进行分解的过程
- 目的是分解模型以控制复杂性
- 战略设计是与传统建模和设计方法的核心区别之一
战略设计包含三部分:领域划分 -> 寻找限界上下文(BC) -> 确定上下文映射
领域划分
- 领域划分是以分离关注点为原则对问题空间的划分
- 子域是领域中某个方面的问题和解决它所设计的一切
- 基于领域划分进行分工协作而非基于需求
- 传统模式的问题:模块分给不同开发人员导致模型重叠
- 基于用户故事分解可以让领域划分清晰化
核心域
- 领域 -> 精炼(分离和萃取) -> (核心域,通用子域,支撑子域)
- 团队尽量减少非核心域的投入
- 从个人发展角度看,程序员尽量投入到核心域的工作
限界上下文
- 限界上下文:是一种语义上的上下文边界。意思是在这个边界里的软件模型组件都有它特定的含义并且做特定的事。一个限界上下文内的组件都是上下文特定的并且语义明确的。
- 为什么需要限界上下文
- 自然语言有歧义,同一个事物在不同场景的含义不一样
- 控制复杂性,便于分工协作
- 限界上下文的划分方式
- 领域故事陈述法
- 事件风暴法
- 基于子域概念提取
- 微服务是限界上下文的一种实现方式,一般一个限界上下文对应一个服务
- 聚合是限界上下文粒度的下限
上下文映射
- 上下文映射是指限界上下文之间的模型映射关系
- 描述团队之间的协作关系以及上下文之间的集成关系
- 决定上下文之间如何集成以及如何设置防腐层
- 上下文映射模式:合伙人、共享内核、客户/供应商、顺从者、防腐层、分道扬镳、开放主机服务、公开语言、大泥球
战术设计
- 对各个 BC 的细节设计过程
- BC 内部的模型结构与完整技术方案
- 战术设计是包括编码的,设计人员和架构师是需要深度编码的
- 类图无法描述跨域限界上下文之间关系的,会淹没在细节之中
分层架构
- 架构分层能够避免模型在实现中被省略或者污染
- 四层架构
- 用户界面层
- 应用层
- 领域层
- 基础设施层
- 六边形架构
- 端口和适配器
- 应用程序
- 领域模型
领域模型
实体和值对象
实体 | 值对象 |
---|---|
ID 相等性 | 属性相等性 |
要跟踪状态变化 | 不变性,可互换 |
- 是什么:具有 ID 相等性且需要跟踪状态变化的是实体,否则是值对象
- 为什么:值对象的实现更简单,技术负担更小,尽可能使用值对象
- 怎么做:基于对象的技术特征、基于更直观的模型特点
聚合和聚合根
聚合就是一组相关对象的集合(比如汽车包含轮胎、方向盘、发动机),我们把它作为数据修改的单元。每个聚合都有一个根和一个边界。 聚合根是聚合所包含的一个特定实体。对聚合而言,外部对象只引用根,而边界内部的对象之间则可以互相引用。 聚合是拥有事务一致性(强一致性)的领域对象组合
- 聚合的原则
- 聚合内的实体适用事务一致性
- 聚合之间适用最终一致性
- 不脱离聚合根修改聚合内部对象
- 聚合根有全局唯一标识,聚合内部实体只有局部标识
- 聚合根可以从资源库获取,聚合内部实体不能
- 聚合的识别
- 实体是否在所有活动中都协同变更
对象构造
- 工厂方法、抽象工厂、Builder 模式
- 生成实体 ID 的方式:程序自动生成、数据库自增 ID、ID 生成器(推荐)
资源库(数据访问层)
- 资源库放到适配层更好
- 资源库最好是依赖接口,可以方便更换存储层实现,比如是数据库还是缓存
领域服务
当领域中的某个重要的过程或转换操作不是实体或值对象的自然职责时,应该在模型中添加一个作为独立接口的操作,并将其声明为领域服务。定义接口时要使用模型语言,并确保操作名称是通用语言中的术语。此外,应该使领域服务成为无状态的。
- 领域服务只包含业务逻辑(不包含事务管理、访问授权、数据格式转换)
- 不属于实体或值对象的逻辑,才可以放到领域服务
- 领域服务是无状态的
应用层
定义软件要完成的任务,并且指挥表达领域概念的对象来解决问题。这一层所负责的工作对业务来说意义重大,也是与其他系统的应用层进行交互的必要渠道。 应用层要尽量简单,不包含业务规则或者知识,而只为下一层中的领域对象协调任务,分配工作,使他们互相协作。
- 应用层的职责
- 事务控制
- 身份认证和访问权限
- 定时任务调度
- 事件订阅(事件监听(适配层)、事件处理(应用层))
- 调用领域层
领域事件
- 是什么:
- 领域中发生的任何领域专家感兴趣的事情
- 领域事件一般由聚合产生
- 领域事件不是技术概念,是通用语言
- 为什么:
- 领域事件能够驱动建模
- 领域事件和很多重要思想相关,比如 CQRS、Event Sourcing
- 领域事件和大数据处理和分析相关
- 发布和订阅方式
外部系统 | 内部系统 |
---|---|
API 定向通知(回调 URL) | 观察者模式 |
API 定时拉取 | 数据库流水 |
消息队列(安全性:SSL) | 消息队列 |
- 事件存储
- 直接使用消息中间件的存储,需要做好冗余备份
- 基于数据库,mongoDB、postgresql、mysql,做好分布式、按时间分区
- 事件处理的要求
- 顺序性:聚合 ID、存储分片、消费分组
- 幂等性:代替分布式事务(状态判断或者去重)
事件风暴法
核心词汇 | 便利贴颜色 |
---|---|
角色 | 黄色 |
策略 | 深紫 |
决策命令 | 蓝色 |
聚合 | 淡黄 |
领域事件 | 橙色 |
读模型 | 绿色 |
外部系统 | 粉色 |
问题/热点 | 紫红 |
- 正向驱动
列出领域事件 -> 选定领域事件 -> 确定决策命令 -> 确定用户或者策略 -> 确定聚合 -> 选定一个事件
- 反向驱动
选定领域事件 -> 确定命令的读模型 -> 由读模型驱动出上游领域事件
参考
《领域驱动设计:软件核心复杂性应对之道》 Eric Evans
《实现领域驱动设计》 沃恩.弗农
《领域驱动设计 Thoughtworks 洞见》