DDD系列 - 第1讲 DDD相关概念入门

news2025/4/16 2:27:37

目录

    • 一、引言
    • 二、 统一语言Ubiquitous Language
    • 三、 三个阶段(战略、战术、实现)
      • 阶段1:战略设计阶段
      • 阶段2:战术设计阶段
      • 阶段3:技术实现阶段
    • 四、限界上下文Bounded Context
    • 五、上下文映射Context Map
      • 防腐层Anti-Corruption Layer
      • 开放主机服务Open Host Service
      • 发布者-订阅者
      • 共享内核Shared Kernel
    • 六、领域模型大杂烩
    • 七、微服务、限界上下文、聚合等的关系

一、引言

领域(Domain),即对应特定行业的业务场景(业务概念、业务概念之间的联系、业务流程),也可简单理解为业务,如电商业务、医疗业务、社保业务、政府业务、车厂业务等。

领域驱动设计(Domain Driven Design,简称DDD),即通过对特定行业的业务场景的分析 来驱动(转换、产生) 软件的架构设计与代码实现,而这里的软件架构设计与是实现即可理解为 微服务的拆分面向对象(封装、继承、多态、高内聚低耦合等)的设计与编程。领域驱动设计,可以简单理解将业务逻辑转换为代码实现一种方法论。

这里提到的面向对象编程,即真正的面向对象,
而非我们后端开发常使用SpringMVC的分层架构时贫血模型

  • service几乎包含全部逻辑实现
  • entity对象直接对应数据库table,仅包含属性,没有业务逻辑

在这里插入图片描述
学生时代老师讲的面向对象是将现实世界中的人、物、逻辑实体等映射为OOP中的类,现实中的物体的状态、操作等映射为类的属性和方法,而随着近些年MVC模式的流行,entity类只有属性(getter/setter、贫血模式),而所有的操作都放在service层实现,通常会先进行数据库的设计,而后将数据库中的table直接映射为entity,后续的编码都是围绕这个强关联数据库table定义的entity进行展开,只不过是使用了面向对象的编程语言,但并不是真正的面向对象的编程思想,倒更像是面向数据库编程,也可以叫做数据模型驱动设计,如此若切换底层数据库实现,则涉及entity的相关代码都需要修改。不过用惯了MVC分层开发模式,写起代码、维护代码时也还是很顺手的,那为什么还要切换到领域驱动设计呢?

使用领域驱动设计,我能想出的理由如下:

  • DDD(限界上下文)有助于微服务的划分
  • DDD将业务逻辑、技术实现进行分离(六边形架构思想),核心业务逻辑实现保持不变,可以自由切换底层技术实现
    • Rest切换gRPC、底层存储MySql切换ES、RabbitMq切换Kafka、切换第三方云存储服务等
    • 最大限度的领域核心代码的复用
  • DDD是面向对象编程经过合理重构后一定会抵达的终点
    • DDD中包括分层架构、六边形架构(内部业务、外部技术)等
    • 高内聚、低耦合用到极致
    • 即便不使用DDD,但是其中的一些架构思想(统一语言Ubiquitous Language、防腐层ACL、依赖注入DI、事件驱动EDA等)还是很值得借鉴的

在学习DDD的过程中你或许会发现DDD中提到的架构思想正是我们平时开发时常用的方式,只不过DDD中给出了一个专有的称呼,那并不奇怪,大家的目的都是一样的,那就是更好的软件的设计,我们都在为了这同一个目的不断进行摸索,如果发现我们的方式和DDD有重叠,那说明我们也许走在正确的道路上,殊途同归…

但是DDD的使用还是具有相当高的门槛的:

  • DDD中的术语多到爆炸,对于刚接触DDD的小白并不友好
  • 相较于MVC分层架构(如SpringMVC),DDD并没有较成熟的代码实现框架
  • 相较于MVC各层代码职责分明,DDD中分层设计(洋葱架构、整洁架构、清晰架构等)、对象及接口设计有一定上手难度
  • DDD中关于限界上下文的划分并没有绝对的标准(事件风暴、用例场景分析、全凭经验等)

现阶段我的结论就是:

  • 我不会轻易使用DDD,但我会借鉴DDD中的架构思想
  • 可以在团队内部先选择个别内部项目使用DDD,熟悉DDD思想并逐步积累DDD架构落地经验
  • 客户方有明确要求使用DDD开发方式的我会使用
  • 待有一天DDD真正成熟了(成为业内标准了)或者 我们自己在使用DDD尝到甜头了,再大面积切换DDD

接下来开始本文的重点,依次介绍DDD的相关概念。

二、 统一语言Ubiquitous Language

业务和软件实现间统一语言,即从业务需求中提炼出统一语言(统一的词汇表),在业务方(或 领域专家)和开发团队间达成一致的理解。需给出领域术语的英文单词描述,之后设计与编码需使用该统一词汇。如此业务方、开发团队间通过统一语言进行沟通,降低了彼此的沟通成本,也加深了团队对领域的理解,看到代码中的术语也可以做到见名识义。无论是否使用DDD,在开发团队间及内部实行统一语言都是非常有益处的。

关于统一语言的具体实践可参见下图:
在这里插入图片描述

三、 三个阶段(战略、战术、实现)

领域驱动设计可整体划分为3个阶段:

阶段1:战略设计阶段

  • 划分 子域
    • 核心域:决定产品和公司核心竞争力的子域,它是业务成功的主要因素和公司的核心竞争力。
    • 通用域:没有太多个性化的诉求,同时被多个子域使用的通用功能的子域。
    • 支撑域:既不包含决定产品和公司核心竞争力的功能,也不包含通用功能的子域。
    • 核心领域即为重要的领域,通用域、支撑域名起到辅助核心领域的作用
    • 核心领域可由更好的团队开发、付出更高的成本,通用域、支撑域可适当降低标准(或者 购买通用服务、外包等)
  • 划分 限界上下文
    • 针对各自子领域 划分 限界上下文Bounded Context、上下文映射Context Map
    • 后续可映射到 微服务的划分、服务间的依赖调用
  • 设计 六边形架构
    • 每个限界上线文即对应一个六边形
    • 上线文映射即为六边形间的交互(上下游、ACL、OHS、EDA等)
    • 六边形中心为核心业务逻辑,外层即为技术实现

注:以下三张图示仅作为图例形式展示,并不存在对应关系。

子域及限界上下文划分如下图:

在这里插入图片描述

限界上下文映射如下图:
在这里插入图片描述
一个限界上下文对应的六边形架构如下图:
在这里插入图片描述

阶段2:战术设计阶段

  • 针对单个限界上下文进行战术设计
  • 战术阶段围绕领域模型进行设计
    • 常用的领域模型:聚合、聚合根、实体、值对象、领域事件、领域服务、应用服务、仓库、ACL
  • 可结合类图进行设计

阶段3:技术实现阶段

  • 使用DDD分层架构,隔离业务逻辑和技术实现,确保最核心的领域层不依赖其他层,反过来让领域之外的代码依赖领域代码,降低了技术升级带来的影响。
  • 可自定义DDD领域模型接口(不同领域模型实现对应的接口) 。
  • 实现领域模型:聚合根、实体、值对象、领域事件、领域服务、应用服务、仓库、ACL等
  • 合理使用DDD架构模式:DDD分层、防腐层ACL、开发主机服务OHS、事件驱动架构EDA、命令查询职责分离CQRS等

关于DDD分层架构可参见六边形架构、洋葱架构、整洁架构、清晰架构的思想,下图为清晰架构分层:
在这里插入图片描述
具体落到代码上分层架构可参见下图:

注:
D3S(DDD with SpringBoot)为本作者使用DDD过程中开发的框架,后续完善后会公开源码。

在这里插入图片描述

综上,
战略设计阶段:宏观划分各领域 → 领域内划分限界上下文,定义上下文之间的关系
战术设计阶段:针对每个限界上下文进行分析,识别领域模型,定义合适的领域模型
技术实现阶段:通过分层架构实现编码,并验证领域模型的合理性,必要时重新回到前面步骤重构领域模型。

四、限界上下文Bounded Context

可以将Bounded Conext拆开来理解,
上下文Context,可推想到语义上下文,例如下面中的这两个“我去”:

小明走路低头看手机,不小心踩进了水坑里,生气地说了一句:“我去!”

小明妈妈问他去不去大姨家,小明回答:“我去。”

都是“我去”但是表示的是不同的意思,需要结合这句话的上线文来理解。

上线文亦可关联到领域(业务场景)上下文,例如下面中的这两个“商品”:

茶城的“商品”

汽车4S店的“商品”

同样的“商品”在不同的领域上下文可以是完全不相同的东西,例如茶叶、汽车,
甚至在同一领域的不同阶段,对应同一事物的的关注点也可能不尽相同,
例如4S点销售人员关注汽车的价格、配置等,而售后维修人员关注汽车的保养周期、零配件等。
所以需要通过领域下文来区分领域模型的含义及相应的关注点

边界Bounded,即领域的边界,上下文的边界,如售前和售后、订单和商品等。边界的终极奥义就是高内聚低耦合,边界内高内聚,不同边界间低耦合(限界上下文仅通过聚合根暴露引用)。边界划分即是一种分而治之的手段,隔离关注点,边界内自治(边界内维持领域模型的一致性和完整性),降低了系统的实现复杂度,各自边界内仅需关注各自的核心业务,边界间通过预先定义好的接口进行交互。

内聚:度量 一个模块内部各个元素 彼此结合的紧密程度。
耦合:度量 模块之间 互相连接的紧密程度。

关于限界上线文的划分,可以从如下三个层面展开:

  • 业务边界(领域逻辑层面)
    • 具体的业务逻辑边界
    • 例如妇产科、儿科、麻醉科等,又或者 人力资源、财务、生产等
  • 工作边界(团队合作层面)
    • 康威定律,组织结构决定软件结构
      • 例如公司开发团队分为商品部、订单部、售后部,那这个三个维度就可以独立为单独的限界上线文。
  • 应用边界(技术实现层面) - 出于以下维度可考虑将其独立为单独的限界上下文
    • 性能要求(高并发、实时…)
    • 功能重用(或者 通用功能)
    • 第三方服务集成
    • 遗留系统

五、上下文映射Context Map

领域驱动设计通过上下文映射Context Map来表达限界上下文间的协作(依赖、调用、通信)关系。可以参考微服务架构下,服务很难独立存在,往往需要多个微服务间协同工作,协同即产生服务间的相关依赖调用,上下文映射即对应服务间的依赖关系。两个限界上下文间的关系是有方向的:

上游Upstream:上游影响下游(可参考河流上游产生的变化会影响到下游,如上游水质不好必定污染下游)。
下游Downstream:下游依赖上游,可理解为下游向上游发起调用。

如果限界上下文间的依赖(服务间通信)不可避免,可以通过如下几种方式降低上下文间的耦合:

防腐层Anti-Corruption Layer

防腐层即隔离层的意思,加入新的一层将核心业务和易变的、不稳定的依赖(其他上线文、其他服务、底层技术平台、第三方服务等)进行隔离,保证核心业务层的稳定。核心业务即为领域层,防腐层即为领域层内定义的接口(后文称为ACL接口),领域层通过这些ACL接口访问其他上下文、其他服务等,接口定义不变,则核心领域逻辑实现不变。而ACL接口的实现在基础设施层完成,基础设施层实现ACL接口,与具体的持久化(Mysql、Redis等)、Rpc(Rest、gRPC、Dubbo等)、Mq(RabbitMq、Kafka等)、第三方平台等打交道。

开放主机服务Open Host Service

开放主机服务(后文简称文OHS)就是定义公开的服务协议(通信的方式、传递消息的格式)。OHS可视为一种承诺,保证开放的服务不会轻易发生变化。公开的服务应该力求标准化,例如采用Rest、gRPC等。

发布者-订阅者

即多个限界上线文(或者 微服务间)采用消息中间件进行通信,通信过程中传递的消息即对应领域事件DomainEvent,需要事先约定好事件的的格式与数据。一个限界上下发布事件,其他多个限界上线文订阅事件,发布者和订阅者通过消息中间件进行解耦,通常用于异步非实时的业务场景。

共享内核Shared Kernel

若多个上线文间依赖共同的子集,可将该共同的子集抽取为多个上线文间的共享内核。分离出来的共享内核应该由上游团队进行维护,需要处理好与下游的团队协作。共享内核可以作为单独的Maven模块,发布到Maven私服上,由需要的下游团队进行依赖,又或者同一个服务内部存在多个限界上下文,则可在服务内部以 模块 或者 包 的形式单独维护共享内核。

关于限界上下文之间的映射关系,更多说明如下:

  • 防腐层(Anticorruption Layer):一个上下文通过一些适配和转换与另一个上下文交互。
  • 开放主机服务(Open Host Service):定义一种协议来让其他上下文来对本上下文进行访问。
  • 发布语言(Published Language):通常与OHS一起使用,用于定义开放主机的协议。
  • 共享内核(Shared Kernel):两个上下文依赖部分共享的模型。
  • 客户方-供应方开发(Customer-Supplier Development):上下文之间有组织的上下游依赖。
  • 遵奉者(Conformist):下游上下文只能盲目依赖上游上下文。
  • 合作关系(Partnership):两个上下文紧密合作的关系,一荣俱荣,一损俱损。
  • 大泥球(Big Ball of Mud):混杂在一起的上下文关系,边界不清晰。
  • 另谋他路(SeparateWay):两个完全没有任何联系的上下文。

六、领域模型大杂烩

名称定义
实体Entiy实体一般对应业务对象,
具有唯一ID、属性(值对象)和业务行为。
值对象Value Object值对象主要为属性的集合(对应一个 或 多个属性),对实体Entity的状态、特征进行语义化的描述(不是单纯的编程语言类型如string、long,而是语义的UserName、PhoneNo、ID等),通常在值对象构造函数参数列表中接收多个属性如constructor(attr1, attr2)并对多个属性进行基本的验证(非空、格式、长度等),值对象通常作为实体Entity的属性。
聚合Aggregate聚合是多个实体、值对象的逻辑边界,这些实体和值对象在业务和逻辑上紧密关联。
聚合根Aggregate Root聚合内仅选择一个实体Entity作为聚合根,聚合内仅允许通过聚合根暴露访问,聚合根是聚合内数据访问、数据修改和持久化的基本单元,聚合根需保证聚合内业务的完整性和一致性。
领域原语Domain Primitive类似于值对象ValueObject,强调将
1)隐性的概念、隐性的上下文 => 显性化、
2)封装多对象行为。
领域事件Domain Event表示领域中发生的重要事件(如采用事件风暴方法则对应事件风暴中的领域事件),事件发生后通常会导致进一步的业务操作,或者在系统其他地方引起反应。
领域服务Domain Service领域服务没有任何属性或数据,只有一个领域行为或动作,不适合放在任何实体内的逻辑行为即可封装为领域服务。
应用服务Application Service通常对应具体的业务场景,通过编排聚合(聚合根)、领域服务、资源库、领域事件、ACL来完成
资源库Repository负责聚合的加载、添加、修改、删除,领域模型的持久化抽象,定义在领域层且仅和领域对象打交道,不依赖具体ORM框架。
工厂Factory工厂负责复杂聚合对象的创建
数据转换器
DataAssembler
DataMapper
DataConvertor
在这里插入图片描述
防腐层ACL
端口Port
网关Gateway
外观Facade
接口隔离层

七、微服务、限界上下文、聚合等的关系

关于微服务、限界上下文、聚合等的关系可见下图:在这里插入图片描述
关于跨实体、跨聚合、跨限界上下文的业务编排可参见如下说明:

同一限界上线文内
业务逻辑 跨多个实体,通过 领域服务实现
业务逻辑 跨多个聚合,通过 应用服务编排、领域事件来实现
不同限界上下文
业务逻辑 跨多个限界上线文,通过 应用服务编排 ACL、领域事件来实现

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/386447.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度学习代码怎么读-小白阶段性思路

深度学习代码怎么读-小白阶段性思路目前思路学习资料读代码工具-chatgpt目前思路 努力上路的小白一枚,麻烦路过的大佬指导一二,同时希望能和大家交流学习~ 和学长、实习老师们交流后的目前思路: 先找到自己研究领域的顶级期刊,…

21 Nacos客户端本地缓存及故障转移

Nacos客户端本地缓存及故障转移 在Nacos本地缓存的时候有的时候必然会出现一些故障,这些故障就需要进行处理,涉及到的核心类为ServiceInfoHolder和FailoverReactor。 本地缓存有两方面,第一方面是从注册中心获得实例信息会缓存在内存当中&a…

AGV机器人出圈:助力产线物流自动化

随着开年档电影《流浪地球2》的热映,里面的四足仿生机器人机械狗“笨笨”、可穿戴的外骨骼机器人等“黑科技”,都让人对机器人的魅力刮目相看,机器人成功“出圈”了,随着智能技术的发展与进步,我们常见的机器人种类越来…

Linux命令之sed

sed,Stream Editor(字符流编辑器)的缩写,简称流编辑器,是操作、过滤、转换文本内容的工具。 常用功能包括结合正则表达式对文件实现快速的增删改查。 工作原理 sed有2个空间来缓存数据,paattern space&am…

Qt交叉编译环境搭建

环境及版本:Deepin 20.3 Qt 5.12.9 arm编译工具 gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz 1.下载Qt源码:qt-everywhere-src-5.12.9.tar.xz,并解压 2.下载arm编译工具: gcc-linaro-7.5.0-2019.12-x86_64_arm…

央企集团是怎么设置信息化、数字化部门的?

在数字经济大潮中,数字化转型已不是企业的“选修课”,而是关乎企业生存和长远发展的“必修课”。在企业数字化转型中,国有企业特别是中央企业普遍将数字化转型战略作为“十四五”时期业务规划的重要内容之一,数字化能力也成为衡量…

代码随想录【Day31】| 455. 分发饼干、376. 摆动序列、53. 最大子数组和

455. 分发饼干 题目链接 题目描述: 假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。 对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸&#xff…

用Docker搭建yolov5开发环境

拉取镜像 sudo docker pull pytorch/pytorch:latest 创建容器 sudo docker run -it -d --gpus "device0" pytorch/pytorch bash 查看所有容器 sudo docker ps -a 查看运行中的容器 sudo docker ps 进入容器 docker start -i 容器ID 将依赖包全都导入到requiremen…

如何将图数据库应用于电影智能推荐

导读 电影,是一种结合视觉与听觉的现代艺术。如今,电影已不单是人们娱乐消遣的生活方式,也逐渐成为国家文化软实力的重要标志之一。据有关数据统计,2021年中国影视行业市场规模达2349亿元,同比增长23.2%,预…

java--IO

IO1.文件流2.常用的文件操作(1)根据路径构建一个File对象(2)根据父目录文件子路径构建(3)根据父目录子路径构建(4)获取文件相关信息(5)目录的操作和文件的删除…

计算机图形学07:有效边表法的多边形扫描转换

作者:非妃是公主 专栏:《计算机图形学》 博客地址:https://blog.csdn.net/myf_666 个性签:顺境不惰,逆境不馁,以心制境,万事可成。——曾国藩 文章目录专栏推荐专栏系列文章序一、算法原理二、…

Git 企业级分支提交流程

Git 企业级分支提交流程 首先在本地分支hfdev上进行开发,开发后要经过测试。 如果测试通过了,那么久可以合并到本地分支develop,合并之后hfdev和development应该完全一样。 git add 文件 git commit -m ‘注释’ git checkout develop //切换…

svn使用

一、SVN概述 1.1为什么需要SVN版本控制软件 1.2解决之道 SCM:软件配置管理 所谓的软件配置管理实际就是对软件源代码进行控制与管理 CVS:元老级产品 VSS:入门级产品 ClearCase:IBM公司提供技术支持,中坚级产品 1.…

【无标题】开发板设置系统时间

开发板设置系统时间环境查看系统时间查看硬件时间设置系统时间设置RTC时间时钟包括硬件时钟和系统时钟,系统时钟就是linux系统显示的时间,用命令 date可以显示当前系统时间;硬件时钟就是硬件自身的时间了。它们两者没有关系的,但是…

如何利用Power Virtual Agents机器人远程打开电脑中的应用

今天我们来介绍如何利用Power Virtual Agents来远程控制电脑。我们的设计思路是在聊天机器人里输入触发短语后打开自己电脑中的题库软件。 首先,进入已经创建好的聊天机器人编辑界面。 新建一个主题后,在“新建主题”中添加“触发短语”。 添加节点后&a…

C++代码优化(3):条款13~17

"野性袒露着灵魂纯粹"条款13:以对象管理资源(1)什么是资源?C中最常使用的资源就是动态内存分配,在系统编程层面上,文件描述符(fd)、互斥锁(mutex)、套接字网络socket……不管是哪一种资源,重要的是,你不使用…

CEC2014:鱼鹰优化算法(Osprey optimization algorithm,OOA)求解CEC2014(提供MATLAB代码

一、鱼鹰优化算法简介 鱼鹰优化算法(Osprey optimization algorithm,OOA)由Mohammad Dehghani 和 Pavel Trojovsk于2023年提出,其模拟鱼鹰的捕食行为。 鱼鹰是鹰形目、鹗科、鹗属的仅有的一种中型猛禽。雌雄相似。体长51-64厘米…

Spark 任务调度机制

1.Spark任务提交流程 Spark YARN-Cluster模式下的任务提交流程,如下图所示: 图YARN-Cluster任务提交流程 下面的时序图清晰地说明了一个Spark应用程序从提交到运行的完整流程: 图Spark任务提交时序图 提交一个Spark应用程序,首…

mysql数据库之存储过程

一、存储过程简介。 存储过程是事先经过编译并存储在数据库中的一段sql语句的集合,调用存储过程可以简化应用开发人员的很多工作,减少数据在数据库和应用服务器之间的传输,对于提高数据处理的效率是也有好处的。 存储过程思想上很简单&…

Mysql常见面试题总结

1、什么是存储引擎 存储引擎指定了表的类型,即如何存储和索引数据,是否支持事务,同时存储引擎也决定了表在计算机中的存储方式。 2、查看数据库支持哪些存储引擎使用什么命令? -- 查看数据库支持的存储引擎 show engines; 或者 …