最近一直在学习DDD的相关概念,主要通过极客欧创新老师的《DDD实战课》和Eric Evans《领域驱动设计》书籍进行阅读,之后记录下核心要点后,通过golang来实战下,进而更好的进行后续进阶。

引言

领域驱动设计(Domain Driven Design,DDD)这个概念最近几年非常火,笔者在最近一段时间对相关内容进行阅读,总体下来感觉这个概念有点大,动不动就要高屋建瓴,大刀阔斧的进行战略设计。同时相关的代码实现大部分是java实现的,无法参考其落地实现。

因此本文首先在总结记录欧创新老师的《DDD实战课》核心知识点的基础上,参考国外一篇博客通过golang实战,完整代码参考我的github工程golang_ddd,读者可以借助这个demo工程更好的学习这些概念。

一、微服务设计为什么需要DDD

在单机和集中式架构这两种模式下,软件无法快速响应需求和业务的迅速变化,最终错失发展良机。分布式微服务的出现,可以解决这些问题。但是微服务拆分困境产生的根本原因就是不知道业务或者微服务的边界到底在什么地方,因此DDD应运而生。

DDD 不是架构,而是一种架构设计方法论,它通过边界划分将复杂业务领域简单化,帮我们设计出清晰的领域和应用边界,可以很容易地实现架构演进。
DDD 核心思想是通过领域驱动设计方法定义领域模型,从而确定业务和应用边界,保证业务模型与代码模型的一致性。
DDD 包括战略设计和战术设计两部分:

  • 战略设计主要从业务视角出发,建立业务领域模型,划分领域边界,建立通用语言的限界上下文,限界上下文可以作为微服务设计的参考边界。
  • 战术设计则从技术视角出发,侧重于领域模型的技术实现,完成软件开发和落地,包括:聚合根、实体、值对象、领域服务、应用服务和资源库等代码逻辑的设计和实现。

二、DDD相关概念

DDD 核心知识体系具体包括:领域、子域、核心域、通用域、支撑域、限界上下文、实体、值对象、聚合和聚合根等概念

2.1 领域、子域、核心域、通用域、支撑域

领域: DDD 的领域就是这个边界内要解决的业务问题域。

在领域不断划分的过程中,领域会细分为不同的子域,子域可以根据自身重要性和功能属性划分为三类子域,它们分别是:核心域、通用域和支撑域。

核心域: 业务成功的主要因素和公司的核心竞争力。
通用域:同时被多个子域使用的通用功能子域是通用域。比如数据库、权限认证等
支撑域:具有企业特性,但不具备通用性。

在不同的场景下,不同的人对核心域的理解是不同的,因此处理方式也会不一样。
一个领域相当于一个问题域,领域拆分为子域的过程就是大问题拆分为小问题的过程。

2.2 限界上下文

限界上下文的定义就是:用来封装通用语言和领域对象,提供上下文环境,保证在领域之内的一些术语、业务相关对象等(通用语言)有一个确切的含义,没有二义性。

限界上下文则定义领域边界,以确保每个上下文含义在它特定的边界内都具有唯一的含义,领域模型则存在于这个边界之内。

每个领域模型都有它对应的限界上下文,团队在限界上下文内用通用语言交流。领域内所有限界上下文的领域模型构成整个领域的领域模型。 理论上限界上下文就是微服务的边界。我们将限界上下文内的领域模型映射到微服务,就完成了从问题域到软件的解决方案。

2.3 实体

  • 业务形态

在战略设计时,实体是领域模型的一个重要对象。领域模型中的实体是多个属性、操作或行为的载体。在事件风暴中,我们可以根据命令、操作或者事件,找出产生这些行为的业务实体对象,进而按照一定的业务规则将依存度高和业务关联紧密的多个实体对象和值对象进行聚类,形成聚合。你可以这么理解,实体和值对象是组成领域模型的基础单元

  • 代码形态

实体的表现形式是实体类,这个类包含了实体的属性和方法,通过这些方法实现实体自身的业务逻辑。在 DDD 里,这些实体类通常采用充血模型,与这个实体相关的所有业务逻辑都在实体类的方法中实现,跨多个实体的领域逻辑则在领域服务中实现。

  • 实体的运行形态

实体以 DO(领域对象)的形式存在,每个实体对象都有唯一的 ID。

  • 模型设计

实体的数据库形态与传统数据模型设计优先不同,DDD 是先构建领域模型,针对实际业务场景构建实体对象和行为,再将实体对象映射到数据持久化对象。

2.4 值对象

学术定义:通过对象属性值来识别的对象,它将多个相关属性组合为一个概念整体。在 DDD 中用来描述领域的特定方面,并且是一个没有标识符的对象,叫作值对象。

通俗定义:值对象本质上就是一个集。那这个集合里面有什么呢?若干个用于描述目的、具有整体概念和不可修改的属性。那这个集合存在的意义又是什么?在领域建模的过程中,值对象可以保证属性归类的清晰和概念的完整性,避免属性零碎。

文章中举例是在描述人这个实体时,除了id,性别,年龄外,还包含了很多地址信息,如果此时打平放到这里,显得人这个概念很零碎,那就定义一个“地址属性集合(省、市、县、街道)”,这就是值对象

2.5 聚合(Aggregate)和聚合根(AggregateRoot)

实体一般对应业务对象,它具有业务属性和业务行为;而值对象主要是属性集合,对实体的状态和特征进行描述。但实体和值对象都只是个体化的对象,它们的行为表现出来的是个体的能力。

领域模型内的实体和值对象就好比个体,而能让实体和值对象协同工作的组织就是聚合,它用来确保这些领域对象在实现共同的业务逻辑时,能保证数据的一致性。

你可以这么理解,聚合就是由业务和逻辑紧密关联的实体和值对象组合而成的,聚合是数据修改和持久化的基本单元,每一个聚合对应一个仓储,实现数据的持久化。

聚合根

聚合根的主要目的是为了避免由于复杂数据模型缺少统一的业务规则控制,而导致聚合、实体之间数据不一致性的问题。

如果把聚合比作组织,那聚合根就是这个组织的负责人。聚合根也称为根实体,它不仅是实体,还是聚合的管理者。

  • 首先它作为实体本身,拥有实体的属性和业务行为,实现自身的业务逻辑。

  • 其次它作为聚合的管理者,在聚合内部负责协调实体和值对象按照固定的业务规则协同完成共同的业务逻辑。

  • 最后在聚合之间,它还是聚合对外的接口人,以聚合根 ID 关联的方式接受外部任务和请求,在上下文内实现聚合之间的业务协同。也就是说,聚合之间通过聚合根 ID 关联引用,如果需要访问其它聚合的实体,就要先访问聚合根,再导航到聚合内部实体,外部对象不能直接访问聚合内实体。

三、DDD分层架构及实战

DDD 分层架构模型。它包括用户接口层、应用层、领域层和基础层,分层架构各层的职责边界非常清晰,又能有条不紊地分层协作。

  • 用户接口层:面向前端提供服务适配,面向资源层提供资源适配。这一层聚集了接口适配相关的功能。
  • 应用层职责:实现服务组合和编排,适应业务流程快速变化的需求。这一层聚集了应用服务和事件相关的功能。
  • 领域层:实现领域的核心业务逻辑。这一层聚集了领域模型的聚合、聚合根、实体、值对象、领域服务和事件等领域对象,以及它们组合所形成的业务能力。
  • 基础层:贯穿所有层,为各层提供基础资源服务。这一层聚集了各种底层资源相关的服务和能力。

业务逻辑从领域层、应用层到用户接口层逐层封装和协作,对外提供灵活的服务,既实现了各层的分工,又实现了各层的协作。因此,毋庸置疑,DDD 分层架构模型就是设计微服务代码模型的最佳依据。

这里以我开发的代码golang_ddd进行示例:

代码都添加了注释,比较好懂。至于是如何一步步实现的,可以参考How To Implement Domain-Driven Design (DDD) in Golang,我在博主的技术上改进采用了mysql存储而非mogodb进行实现。
实战后,你将熟悉这些概念:

  • 实体 - 可修改的带标识的结构体对象
  • 值对象- 不可修改,不带标识的结构体
  • 聚合- 将实体和值对象合并,将要存储
  • 仓储- 一种实现,用于存储聚合和其他信息【可以学习到依赖倒置的思想】
  • 工厂- 一种设计模式,用于创建复杂结构体,同时使得其他domain快速创建实例
  • 领域- 仓储和子服务的业务逻辑组装实现

在这些都完成后可以更进一步的对该工程进行重构,可参考文章:how-to-structure-ddd-in-go

四、参考

  • 掘金系列博客:https://juejin.cn/post/7004002483601145863
  • Eric Evans《领域驱动设计》《实现领域驱动设计》
  • 《微服务社交架构模式》