什么是DDD?
DDD 领域驱动模型。其特点主要体现在以下几个方面:
- 统一语言,消除不同角色人员对应用的不同理解。
- 独立的领域模型,确定业务和应用的边界。
- 领域内实现追求贴近业务本身,确保业务模型与代码实现的一致性。
- 其根本是为了降低业务复杂度。追求业务架构与系统架构的一致性,提升复杂系统的扩展能力以及演进能力。
主要通过合理的领域模型拆分,实现软件层面功能模块的解耦。在指导微服务拆分上有天然的优势。
与微服务的对比
微服务的出现主要是为了解决并发场景下单体服务性能瓶颈问题而出现的。
- 敏捷开发
- 独立部署
- 弹性伸缩
- 复杂度降低
微服务侧重于解决资源、性能瓶颈,关注进程之间的通信、容错、故障隔离,追求服务的独立开发、测试、构建和部署,实现去中心化的数据管理和服务治理。
DDD侧重于建立统一的业务语言。从业务领域角度划分领域边界,通过业务抽象,建立领域模型,并维持业务和代码的逻辑一致性。
DDD 设计的初衷
最初DDD提出是在04年,那时还没有微服务。当时提出的主要目的是为了解决高度复杂业务带来的设计、实现、扩展、难以演进的问题。
其思想是:通过划分合理的业务领域边界拆分业务,降低业务复杂度,降低技术实现复杂度。
就像是任务的拆解。我们要登月。很复杂,而且不同人思路不同,拆分方式也不同。那么这个环节就很依赖拆分人员的素质。这个环节其实就跟微服务的拆分一样,如果没有做好,一样会带来严重的影响。
那么如何做好这个环节呢? DDD 提出了战略设计的指导思想。
有了战略设计,我们划分好了领域边界,设计好了领域对象,那么我们就需要与代码实现中的代码对象建立映射关系,将业务架构和系统架构进行绑定,这个过程中如何让我们的系统架构更好的配合业务架构呢?DDD也提出了一套战术设计。战术设计实现对于开发人员要求较高。
什么时候应该使用DDD?
当业务越来越复杂,演进越来越困难,各个功能边界模糊不清时就应该采用DDD的思想重新调整系统设计了。虽然DDD本质是为了降低业务复杂度的,但却一直没有火起来,直到随着微服务的兴起,服务拆分问题一直是微服务实现的首要难题。但是发现DDD的思想(领域划分)很适合用于指导服务拆分,所以DDD也随之开始热了起来。
DDD的战略设计思想
限界上下文/领域/子域
限界上下文定义领域边界
一个领域中可以有多个子域,相对于领域的限界上下文来说,领域的限界上下文中包含多个子域。每个子域也都有各自的限界上下文。
在同一个限界上下文中,有统一的语言来说明业务。
DDD的战术设计思想
实体
实体业务形态:领域模型中的实体。属性,行为,操作。
实体代码形态:实体类。属性、方法。【充血模型】与这个实体相关的命令操作在实体中完成,夸多个实体的在领域服务中完成。
实体运行形态:强调实体的唯一标识。只要唯一标识不变,其他属性再变化也是同一个实体。
实体数据库形态:一个实体可能对应多个表,也可能对应一个表。
值对象
值对象就是一些具体属性的集合。与实体相比,没有唯一标识,也没有具体的行为、动作。通常实体中会包含一些值对象。
值对象通常只有一个数据初始化的行为,以及一些简单的不涉及数据修改的行为。虽然在物理层面独立出来了,实际上还是实体的一部分。
不过也有一些特殊的共享型值对象,有自己的限界上下文,唯一标识,持久化对象,比如数据字典。
值对象集合 一定要使用单独的实体类么?
如果使用单独的实体类,在代码实现层面将会增加很多额外的工作量。如果不用又无法对其进行统一的赋值管理。从业务角度来说单独实体类更容易理解,但是实现层面比较冗余。而且,相对于其表现出来的优点(易理解,易管理)其缺点更难以让人接受(一会变为实体类【读库】,一会又要拆分出属性【存库】),那么为什么还要有值对象呢?
为什么需要值对象?
其实DDD设计值对象是为了减少数据表的数量,简化数据表的关系。实现从数据建模为中心向领域建模为中心的转变。
以人员地址为例:
| id | name | age | gender | province | city | country | street |
|---|---|---|---|---|---|---|---|
| 1 | 张三 | 30 | 男 | 北京 | 北京 | 昌平 | 沙河 |
如果这里采用值对象的设计,则可以使用一个 Address 值对象来作为 地址集合。
表结构方面大概率是按照表格中的形式来去定义的,Address值对象分为4个字段来存储。(甚至如果表属性越来越多,还会把地址作为单独的表拆分出来,总之是符合数据库设计规范的)
如果使用了Address值对象,表结构依然如此设计,势必会非常的麻烦。所以,既然使用DDD,使用领域建模为中心那就忽略数据模型,直接将数据库表改为。
| id | name | age | gender | address |
|---|---|---|---|---|
| 1 | 张三 | 30 | 男 | {"province":"北京","city":"北京"} |
很显然,这样的表结构会变的很简洁,如果采用这种设计,相信我们能简化不少的表结构。
但是缺点也很明显,我有需要要查询北京市下的所有用户。此时就GG了。所以也要慎重选择。
聚合/聚合根
聚合是联系紧密的实体。一个聚合内包含了多个密切关联的实体对象。
聚合根是聚合的管理者,也是其中的一个实体。可以这么理解,聚合内有多个实体对象,但是只有一个实体能够作为业务的入口,比如用户下单场景,用户,商品,订单三个实体就是一个聚合,用户就是聚合根,由用户来执行添加商品,下单操作。
同一聚合内的实体之间可以直接使用对象引用的方式,不同聚合内的实体只能采用唯一标识引用。
聚合可以作为微服务划分的最小单元,因此在跨聚合层面的交互也要尽可能的解耦。
聚合根与实体的关系和实体与值对象的关系区别
聚合根作为跨聚合依赖的入口,提供了一些聚合整体的业务实现,可以理解为是一个门面层,每个实体内部也都会有实体各自的实现。
理解整理
领域:公司
子域:部门
聚合:项目组
聚合根:项目负责人
实体:项目人员(产品、研发、测试)
值对象:技能、工作项
以上对应一下理解起来就相对清晰一些,跨项目组的沟通通常都是负责人来对接,其他组负责人不能直接对接组内人员。