什么是DDD领域驱动设计?
2004年Eric Evans 发表Domain-Driven Design –Tackling Complexity in the Heart of Software (领域驱动设计 )简称Evans DDD,领域建模是一种艺术的技术,它是用来解决复杂软件快速应付变化的解决之道 .
DDD是一种设计思想,它是基于事件风暴,使用通用语言,对业务进行领域建模,通过限界上下文对业务进行合理的领域拆分,使得领域模型更好地转向微服务和落地,从而解决复杂系统难以理解,难以演进,也可以解决服务业务界限难以界定的问题。
战略设计
- 建立领域模型,划分服务边界,建立通用的限界上下文
战术设计
- 侧重于领域模型的实现,从领域模型转向微服务的设计和落地
DDD 的核心知识体系,具体包括:领域、子域、核心域、通用域、支撑域、限界上下文、实体、值对象、聚合和聚合根等概念。
为什么要使用DDD?
Why DDD – 问题
开发:产品花太多时间调研,给我们太少时间开发
业务:怎么加这么小一个功能都要这么长时间
产品:你知道这个产品逻辑有多复杂?又是第一次,没人做过
测试:系统太复杂,来不及仔细测试
用户:这个产品稳定性怎么这么差
新来的开发:怎么文档都没有,我需要n周时间熟悉系统
领导:我只看结果,别给我找借口
老婆:你再996,我就离婚
问题分析
业务到产品,只是分析,无设计的反馈,产品人员的设计没有技术实现作为实时反馈,导致错误百出设计错误,加上沟通错误在下阶段的实现中被放大,设计的知识遗失。
解决方案 - DDD
业务、产品、开发一起
业务:领域专家,提供领域知识和验证软件实现
产品:协调沟通;提供功能、非功能需求;开发领域测试案例;UI设计
开发:与业务和产品一起开发统一语言,设计并实现领域模型
新的开发模式
DDD核心概念
领域驱动全景图
统一语言
统一语言是一个问题域里,所有相关人员所使用,并且理解(不用再解释) 的共同语言
- 产品、业务、开发、测试共用
- 每个问题域或限界上下文都有各自的通用语言。同样的术语在各个领域里意义不一样
限界上下文,领域和子域
限界上下文
• 是业务和语义的边界
领域
• 是问题域
核心域
• 企业的核心竞争力,需自建
支持域
• 具有企业特性,例如数据字典,可外包开发
通用域
• 没有企业特性的功能,可外购产品来构建能力
聚合
一个聚合包含一个或多个实体/值对象
一个聚合定义了一个数据一致性边界,例如:聚合内所以的实体保持事务的一致性
每个聚合都有一个聚合根和一个边界。
边界定义了聚合中应该包含什么。
每个聚合都有一个根实体,这个根实体做聚合根
聚合根是聚合中唯一允许被外部引用的元素,在聚合边界内,对象之间可以相互引用。
举个简单的例子,一个电脑包含硬盘、CPU、内存条等,这一个组合就是一个聚合,而电脑就是这个组合的聚合根
聚合的特点:高内聚、低耦合,它是领域模型中最底层的边界,可以作为拆分微服务的最小单位,但我不建议你对微服务过度拆分。但在对性能有极致要求的场景中,聚合可以独立作为一个微服务,以满足版本的高频发布和极致的弹性伸缩能力。一个微服务可以包含多个聚合,聚合之间的边界是微服务内天然的逻辑边界。有了这个逻辑边界,在微服务架构演进时就可以以聚合为单位进行拆分和组合了,微服务的架构演进也就不再是一件难事了。
聚合根的特点:聚合根是实体,有实体的特点,具有全局唯一标识,有独立的生命周期。一个聚合只有一个聚合根,聚合根在聚合内对实体和值对象采用直接对象引用的方式进行组织和协调,聚合根与聚合根之间通过 ID 关联的方式实现聚合之间的协同。
聚合中的不变性
不变性定义:无论何时数据发生变化,都必须满足所有一致变化的规则 ,俗话:同生死。
聚合内部的不变量必须在每次事务完成时满足。可以用仓储来实现。
一些依赖关系只能在某些特定的时刻通过事件借助有事务支持的服务来完成,或通过线程安全模式实现原子操作。
实体
实体就是领域中需要唯一标识的领域概念。因为实体有生命周期,实体从被创建后可能会被持久化到数据库,然后某个时候又会被取出来。
实体的特点:有 ID 标识,通过 ID 判断相等性,ID 在聚合内唯一即可。状态可变,它依附于聚合根,其生命周期由聚合根管理。实体一般会持久化,但与数据库持久化对象不一定是一对一的关系。实体可以引用聚合内的聚合根、实体和值对象。
实体管理自己的属性和行为,并暴露行为
值对象
值对象的特点:无 ID,不可变,无生命周期,用完即扔。值对象之间通过属性值判断相等性。它的核心本质是值,是一组概念完整的属性组成的集合,用于描述实体的状态和特征。值对象尽量只引用值对象。
比如一个用户的Address为例,如果两个用户的地址是一样的。也就是说只要地址信息一样,我们就认为是同一个地址。
领域事件
领域事件是领域模型中非常重要的一部分,用来表示领域中发生的事件。一个领域事件将导致进一步的业务操作,在实现业务解耦的同时,还有助于形成完整的业务闭环。
举例来说的话,领域事件可以是业务流程的一个步骤,比如投保业务缴费完成后,触发投保单转保单的动作;也可能是定时批处理过程中发生的事件,比如批处理生成季缴保费通知单,触发发送缴费邮件通知操作;或者一个事件发生后触发的后续动作,比如密码连续输错三次,触发锁定账户的动作。
服务
领域中的一些概念不太适合建模为对象,即归类到实体对象或值对象,因为它们本质上就是一些操作,一些动作,或者是个业务逻辑,而不是事物。他往往会协调多个领域对象来共同完成某个业务。
果强行将这些操作职责分配给任何一个对象,则被分配的对象就是承担一些不该承担的职责,从而会导致对象的职责不明确很混乱。
领域服务是以动词开头来命名的,比如资金转帐服务可以命名为TransferMoneyService。
模型(实体)与服务(场景)是对领域的一种划分,模型关注领域的个体行为,场景关注领域的群体行为,模型关注领域的静态结构,场景关注领域的动态功能。
往仓储中存储(add、remove)、移除对象基础平台是划分在领域服务中。
服务的类型和职责
按照分层架构设计出来的微服务,其内部有 Facade 服务、应用服务、领域服务和基础服务。
各层服务的主要功能和职责如下。
Façade服务:位于用户接口层,包括接口和实现两部分。用于处理用户发送的 Restful 请求和解析用户输入的配置文件等,并将数据传递给应用层。或者在获取到应用层数据后,将 DO 组装成 DTO,将数据传输到前端应用。
应用服务:位于应用层。用来表述应用和用户行为,负责服务的组合、编排和转发,负责处理业务用例的执行顺序以及结果拼装,对外提供粗粒度的服务。
领域服务:位于领域层。领域服务封装核心的业务逻辑,实现需要多个实体协作的核心领域逻辑。它对多个实体或方法的业务逻辑进行组合或编排,或者在严格分层架构中对实体方法进行封装,以领域服务的方式供应用层调用。
基础服务:位于基础层。提供基础资源服务(比如数据库、缓存等),实现各层的解耦,降低外部资源变化对业务应用逻辑的影响。基础服务主要为仓储服务,通过依赖倒置提供基础资源服务。领域服务和应用服务都可以调用仓储服务接口,通过仓储服务实现数据持久化。
基础设施
主要功能是对领域模块进行持久化的,在这个层中你需要把领域对象序列化到指定的元件中,可能是数据库,文件或者内存对象,当然它也要提供从物理元件取出数据到领域模型的功能,这是对应的。
即仓储和缓存、日志等一系列。
仓储
它属于基础设施。
领域模型中的对象是一个和数据库打交道的过程。从更广义的角度来理解,我们经常会像集合一样从某个类似集合的地方根据某个条件获取一个或一些对象,往集合中添加对象或移除对象。也就是说,我们需要提供一种机制,可以提供类似集合的接口(crud)来帮助我们管理对象。仓储就是基于这样的思想被设计出来的;