为什么需要DDD?
我们经常讲技术为业务服务,架构设计需要对业务充分理解,在面向复杂的业务场景时,会面临诸多问题:
- 复杂系统设计:业务系统多、业务类型多、业务相互耦合,有没有合适的方法来指导模块的边界开发?
- 多团队协同:业务系统边界划分不清,系统间依赖复杂,往往一个边界的理解就要扯半天,有没有统一的语言来进行建模,让业务同学和技术同学都看得懂并理解一致?
- 设计和实现耦合:缺少设计文档,业务及功能性代码混杂,有时业务代码可能不到10%,有没有什么方法可以讲业务需求准确转换为软件设计,有些代码进行有效的聚合,达到业务和技术的平衡?
- 缺乏灵活扩展:针对频繁变化的业务,有没有什么方法可以有效支持模型和服务如何支持场景的灵活扩展?特别是商业环境、商业流程和规则多变,同时变化不可预测,对架构带来很大挑战
- 微服务拆分:怎么确定服务的业务边界和应用边界?微服务的拆分粒度应该多大,怎么拆分设计才算合理?
- 中台规划:如何能够清晰的识别和发现业务模型中的共享和联通部分,定义出哪些共享能力中心?
DDD的价值
- 统一语言:团队成员会在有界的上下文中有意识的形成统一语言,便于沟通,减少分歧
- 业务领域知识沉淀:业务通过核心稳定的领域模型,领域知识进行传递,业务相应能力提升
- 边界清晰的设计方法:用领域模型划分出的边界,来界定那些需求是合理的,哪些需求应该在什么地方实现,不断拉齐团队内成员对需求的认知,让设计更加清晰和规范,分而治之,控制规模
- 关注点分离:领域模型与数据模型分离,业务复杂度与技术复杂度分离,保持结构清晰,应对不可预测性挑战
DDD核心切入点
DDD中比较关键的几个切入点是通用语言、领域、限界上下文
通用语言 Ubiquitous Language
通用语言解决在业务人员和技术人员协作过程中非常重要的问题,即团队所有人怎么讲同一种语言?通用语言是提炼领域知识的产出物,获得统一语言就是需求分析的过程,也是理解领域知识的过程。团队中各个角色就系统目标、范围与具体功能达成一致的过程。在实际过程中,可能由团队所有相关角色参加,比如领域专家、产品经理、业务架构师、技术架构师、开发人员。
通用语言可以定义公共术语,减少概念混淆,消除歧义和理解偏差,提升需求和知识消化的效率;达到概念和代码的统一语言,连接概念和实现。通用语言也需要行业参考,如果有一些领域模型有通用知识参考,则可简化通用语言的过程。
领域 Domain
领域是来确定范围和边界的,DDD将业务上的问题将其限定归属在特定的边界内,而这划分出来的一个个边界就可以叫做领域。为了降低业务理解和系统实现的复杂度,DDD会将领域进一步划分成更小力度的小问题,也就是子域,子域根据自身重要性和功能属性又可以划分为三类子域:
核心域:决定产品和系统核心竞争力的子域是核心域,它是影响产品和业务成功的主要因素,比如电商系统中关注的会员、商品、订单、交易、库存、营销等
通用域:没有太多个性化的诉求,同时被多个子域使用的通用功能子域是通用域,比如统一的认证和权限管理系统
支撑域:不包含决定产品和公司核心竞争力的功能,也不包含通用功能的子域,但该功能子域又是产品所必须的,它就是支撑域。比如某个特定领域的数据字典。
限界上下文 Bounded Context
我们在平时沟通中为了避免同样的词语产生歧义,我们会把这个词语带入到语言上下文中去理解其语义。比如谈到“苹果”,有人可能想到平时吃的水果,有人也可能想到苹果手机。
限界上下文是一个显式的概念性边界,领域模型都存在于这个边界之内,出了这个边界就不能确保这个含义。而上面提到的通用语言必须限制在这个限界上下文之中。在微服务设计中,一般一个限界上下文理论上就可以设计为一个微服务。
那么有了边界后,我们如何做到领域之间的交互呢?我们通过上下文映射的方式,一般方法有通过合作关系(一荣俱荣,一损俱损)、防腐层(通过一些适配和转换)、共享内核(依赖部分共享的模型)、依赖关系(有组织的上下游依赖)等,其中需要特别关注防腐层,强调单独一层完成上下游的交互,隔离业务逻辑,在实际过程中会比较实用,比如在商品和采购子域提供防腐层,将商品的变更进行收口,隔离后端业务实现。
DDD核心结构概念
实体 Entities
实体是一个具有唯一身份标识的对象,并且可以在相当长的一段时间内持续地变化。我们可以对实体做多次修改,一个实体对象可能和它先前的对象大不相同,但拥有相同的身份标识(identity),依然是同一个实体。对这些对象而言,重要的不是其属性,而是其延续性和标识,我们把这样的对象称为实体。
另外,实体具有可变性,这里需要引出两个概念,贫血模型还是充血模型。贫血模型类似我们熟悉的Bean或者DO对象,一般只有getter和setter方法,只作为保存状态或者传递状态,但并不包含业务逻辑,这种只有数据没有行为的对象不是真正的领域对象, DDD中的实体属于充血模型,会封装包含这个实体相关的所有业务逻辑,它不仅是多个业务属性的载体,也是操作或行为的载体。
值对象 Value Object
值对象用来描述领域的特定方面,并且是一个没有标识符的对象。值对象本质上就是一个集合,这个集合中包含若干个用于描述目的、具有整体概念和不可修改的属性。它可以避免属性零碎,让属性归类更加地清晰,从概念理解上也更加完整。
值对象与实体的区别在于:值对象一般依附于实体而存在, 是实体属性的一部分,而非独立存在。值对象没有唯一标识,当任何属性发生变化时, 都意味着新的值对象产生。值对象功能单一,一般是贫血模型。
聚合 Aggregates
聚合是可以被视为⼀一组单个领域对象的集合,聚合由根实体,值对象和实体组成,作为一个整体被外界访问,。比如一个包含多条记录的订单,每条记录都是单独的对象,但订单(连同所有记录)一起视为单个聚合是有用的(封装业务规则)。
聚合通过定义清晰的所属关系和边界,并避免错综复杂的对象关系⽹网来实现模型的内聚。在聚合边界内,对象之间可以相互引用。聚合之间,聚合根是聚合中唯一允许被外部引用的元素,比如订单ID。
领域服务 Services
领域中的一些概念不太适合建模为对象,即归类到实体对象或值对象,因为它们本质上就是一些操作,一些动作。它们代表了领域中的一个重要的行为,所以不能忽略它们或者简单地把它们合并到某个实体或者值对象中。这些操作或动作往往会涉及到多个领域对象,并且需要协调这些领域对象共同完成这个操作或动作。领域服务还有一个很重要的功能就是可以避免领域逻辑泄露到应用层。因为如果没有领域服务,那么应用层会直接调用领域对象完成本该是属于领域服务该做的操作。
识别领域服务,主要看它是否满足如下特点:服务执行的操作代表了一个领域概念,这个领域概念无法自然地隶属于一个实体或者值对象;被执行的操作涉及领域中的其他对象;操作是无状态的。
领域建模常用方法
todo 实际项目分析补充
用例分析法
用例分析是比较通用的领域建模方法,可以在比较传统需求调研过程中再结合领域模型的设计思路进行,核心是通过通过需求、场景、规则、流程等梳理用例,进而规划领域模型。
首先,用例分析的基础是做好需求调研,大致的步骤如下:
- 梳理领域概念:梳理出领域内我们关注的概念、用力的关系,并统一交流词汇,形成统一语言;
- 梳理业务规则:梳理出领域内我们关注的各种业务规则,DDD中叫不变性(invariants),比如唯一性规则、促销互斥叠加等;
- 梳理业务场景:梳理出领域内的核心业务场景,比如电商领域的订单寻源、库存锁定、商品价格计算等、优惠券核销等业务场景;
- 梳理业务流程:梳理出领域内的关键业务流程,比如订单处理流程,分单拆单逻辑,逆向退款逻辑等
用例整理好后,可以根据语义来整理用例,进而整理领域模型,大概步骤如下:
- 收集用例:从领域、规则、场景、流程中进行提取,收集相应的名词、动词、形容词
- 提取实体:从名词中定位出主要实体,比如商品、SKU、品类等
- 提取属性:从形容词中添加实体属性,比如颜色、价格等
- 添加关联:动词添加实体和实体之间的关联,比如商品“包含”SKU,卖家“开设”店铺等
- 完善模型:识别出初步模型,验证并迭代模型,同时补充用例验证模型、业务流程验证模型
四色建模法
四色建模在实际中也比较常用,有几个核心概念:
时间(Moment-Interval):具有可追溯性的记录运营或管理数据的时刻或时段对象,用粉红色表示
人货场(Party-Place-Thing Archetype):也叫PPT,代表参与到流程中的参与方/地点/物,用绿色表示
角色(Role):在时标型对象与 PPT 对象(通常是参与方)之间参与的角色,用黄色表示
描述(Description):对 PPT 对象的一种补充描述,用蓝色表示
简单说,四色法关注的是,某个人(Party)的角色(PartyRole)在某个地点(Place)的角色(PlaceRole)用某个东西(Thing)的角色(ThingRole)做了某件事情(MomentInterval)
事件风暴法
事件风暴也称为事件建模,类似头脑风暴,可以快速分析复杂业务领域,完成领域建模的目标。关注如下元素:
事件 -> 某个动作的结果
属性-> 事件的输入、输出
命令-> 某个动作
实体-> 命令的触发者
简单理解就是谁(实体)在何时(时间)基于什么(输入)做了什么(命令、动作)产生了什么(输出)影响了什么(事件)。
事件风暴强调正确的人(业务⼈员,领域专家,技术⼈员,架构师,测试⼈员等关键⻆色都要参与其中),开放空间(有足够的空间可以将事件流可视化,让⼈们可以交互讨论),即时贴(至少三种颜色),关联的人充分讨论,集体决策,从价值角度来审视业务流程的合理性,领域事件容易创建业务人员和非业务人员的共识。
DDD分层架构
展现层:它负责向前台显示信息和解释用户命令,完成前端界面逻辑。展示层的组件实现用户与应用交互的功能。一般建议用MVC,MVP或者MVVM模式来分隔这些组件为子层。
应用层:它是很薄的一层,负责展现层与领域层之间的协调,也是与其它系统应用层进行交互的必要渠道,例如事务、执行单位操作、调用应用程序的任务。应用层要尽量简单,不包含业务规则或者知识,不保留业务对象的状态,只保留有应用任务的进度状态,更注重流程性的东西。它只为领域层中的领域对象协调任务,分配工作,使它们互相协作。类似于Façade模式,调用领域层和基础设施层来完成应用的用例。
领域层:它是业务软件的核心所在,包含了前面提到的核心概念,如领域对象(实体、值对象)、领域服务以及它们之间的关系,负责表达业务概念、业务状态信息以及业务规则,具体表现形式就是领域模型。领域驱动设计提倡充血模型,即尽量将业务逻辑归属到领域对象上。
基础设施层:它向其他层提供通用的技术能力,为应用层传递消息(API 网关等),为领域层提供持久化机制(如数据库资源、中间件交互、缓存、MQ消息)等,屏蔽技术底座能力(如底层服务的健康度检查、配置参数等)。
DDD与传统设计方式不同
DDD给我们带来的是设计模式的改变。DDD的设计模式与传统的面向数据驱动的开发模式有显出区别,这一点特别需要技术同学在具体操作时关注。数据驱动是从数据出发,先梳理ER图,设计数据库表,编写DAO,然后进行业务实现。而领域驱动设计从领域出发,分析领域内模型及其关系,并进行领域建模,设计核心业务逻辑,进而再进行技术细节实现。其核心思想是通过领域驱动设计方法定义领域模型,从而确定业务和应用边界,保证业务模型与代码模型的一致性。在DDD中,领域模型和数据模型是解耦的,有时也不是一一对应,因此,如果一开始应用DDD进行设计时,一定要跳出数据模型优先的束缚,不要让领域模型被数据模型绑架,先设计出合理的领域模型是首要任务。