你需要知道DDD基本知识

news2025/1/10 15:58:27

0 概述

2004 年埃里克·埃文斯(Eric Evans)发表了《领域驱动设计》(Domain-Driven Design –Tackling Complexity in the Heart of Software)这本书,从此领域驱动设计(Domain Driven Design,简称 DDD)诞生;领域驱动设计这一理念迅速被行业采纳,时至今日仍是绝大多数人进行业务建模的首要方法。随着Martin Fowler 提出微服务架构[2],DDD也迎来了新的时代。
 DDD 是一种架构设计的方法论;在具体落地的时候分为战略设计和战术设计,这两个阶段用于指导领域模型的设计和实现。DDD概念比较多比如:领域、子域、核心域、通用域、支撑域、限界上下文、聚合、聚合根、实体、值对象等等;这些名词,都是关键概念,但它们实在有些晦涩难懂,可能导致你还没开始实践 DDD 就打起了退堂鼓。因此,本文将结合自己工作理解对DDD相关概念总结。

1 战略设计

战略设计主要从业务视角出发,建立业务领域模型,划分领域边界,建立通用语言的限界上下文,限界上下文可以作为微服务设计的参考边界。

1.1 统一语言

定义:统一语言(Ubiquitous Language)是一种业务方与技术方共同使用的共同语言,业务方与技术方通过共同语言描述业务规则与需求变动,共同语言为双方提供了协作沟通基础。这里的业务方泛指一切非最终软件实现者(如客户、产品、业务、BI、UED等)。早期共同语言形式:用户画像(User Persona)、用户旅程能够从流程角度有效地构成共同语言,数据字典(Data Dictionary)是从软件实现侧形成的共同语言。
问题:DDD是一种模型驱动设计方法,为什么不直接使用模型模型作为统一语言呢?
在这里插入图片描述
直接使用模型作为统一语言,其效果并不理想主要原因有:1)业务方难以直接把模型和他们对系统的理解关联到一起,这主要是因为业务方看待系统的角度和开发人员不同,业务部方大多数习惯从业务维度去感知系统,如流程、交互、功能、规则、价值等去描述软件系统。2)模型则偏重于数据角度,描述不同了业务维度下,数据如何改变,以及如何支撑对应的计算与统计。3)模型是从已知的需求中总结提炼的知识,模型无法表达未知需求中尚未提炼的知识。此外统一语言可以作为中间的隔离层、提供句够的缓冲来帮助反馈模型的不足,同时也更能满足人与人之间的交流的需求。
统一语言案例:(PS:统一语言形式不重要,核心是统一语言要和模型关联,且需要多方认可&承认并达成一致)。
统一语言包含哪些东西:

  • 领域模型;源自领域模型的概念与逻辑。
  • 术语和概念;定义和约定一系列术语和概念,用于描述和表达领域中的事物、关系和行为。
  • 限界上下文;围绕领域模型设置的边界。
  • 规约和约束;对于领域模型和领域行为的规约和约束。
1.2 限界上下文

定义:限界上下文(Bounded Context)是一个显示的边界,用来封装通用语言和领域对象,提供上下文环境,保证在领域之内的一些术语、业务相关对象等(通用语言)有一个确切的含义,没有二义性。可以拆解为了两次词来理解,即限界和上下文;限界就是领域的边界,上下文则是语义环境。
Eric Evans 用细胞来形容限界上下文,因为“细胞之所以能够存在,是因为细胞膜限定了什么在细胞内,什么在细胞外,并且确定了什么物质可以通过细胞膜。”这里,细胞代表上下文,而细胞膜代表了包裹上下文的边界。

案例:我的一天建模,早上我乘坐地铁到公司上班,坐上地铁上我打开手机进入天猫好房喵屋会签到,然后我又打开微信读书读了会书并买了年卡,9点半之前到达公司开始工作。
在这里插入图片描述
不难看出下文文其实动态业务流程被边界(限界)静态切分的产物,所以产生了出行上下文、签到上下文等。每限界上下文提供不同业务能力,以满足当前上下文角色的目标;限界上下文划定领域知识的边界,形成了各自的知识语境。
案例:物流运输系统,该系统需要支持集装箱在铁路运输和公路运输的多式联运,需要计算每次多次联运的运费,以管理公司与委托公司的账目。
问题:系统定义运输上下文和财务上下文,运费计算是否可以放到财务上下文?如果从知识和能力的角度去理解,财务上下文的领域对象不具备计算运费的领域知识,不了解运输各种费用怎么计算的。缺乏这些知识自然就不具备计算运费的能力。如下图所示,财务上下文通过运输上下文,做账目结算,生成账单。限界上下文之间的复用体现对业务能力的复用而非对知识语境边界内的模型复用。
在这里插入图片描述
限界上下文特征:领域模型知识语境、业务能力纵向切分、自治的架构单元(最小完备、自我履行、独立进化、稳定空间)。
如下图所示,模块是从技术维度横向切分,再从领域维度进行纵向切分;限界上下文则是先从领域维度进行纵向切分,再从技术维度对限界上下文进行横向切分。
在这里插入图片描述
案例:供应链的商品模型,采购、订单、运输、库存都会用到商品信息(Product),但是他们所关心领域知识不一样。如果没有边界的,Product类就要包含与之相关的领域知识,使得Product领域模型变得越发臃肿。对于这种情况,可以根据限界下文拆分出不同的Product类,比如在采购上下文Product只会关心采购相关的领域知识。限界上下文告诉我们,同一个概念,不必总是对应于一个单一模型,也可以对应于多个模型。
在这里插入图片描述

1.3 上下文映射

定义:上下文映射是指将不同的限界上下文(Bounded Context)之间的概念和模型进行映射(Context-Mapping)和协调的过程。限界上下文之间映射,表达了不同领域之间如何进行交互和协同,领域之间的交互是一个复杂的问题,很多时候不仅仅和技术相关,更重要还会涉及到组织结构或者责任分配。比如:XXX领域为啥不按照我的要求提供数据,这个时候到底是谁问题呢,当我为多个领域提供服务时候,他们都对我提出很多要求。
案例:用户下单为例, 需检查商品库存量、提交订单时候需要锁定库存、和使用优惠。由此就产生了订单上下文和库存上下文、营销上文的协作必然带来领域知识传递;如果上游的提供的服务总在变化,订单开发者就需要采用一定的设计手段来避免服务变化带来的干扰。
在这里插入图片描述
DDD上下文映射主要有解决方案:共享内核、防腐层、开放主机服务、客户-供应方、遵奉者等。

1.4 核心域、支撑域、通用域

领域在不断细分&拆解不同子域,这些子域可以根据自身的重要性和功能属性,可以分为三大类,即:核心域、通用域、和支撑域,如下图所示,其中核心域是重心,支撑域和通用域为了支撑核心域的。值得说明是核心域只是一类统称,核心域可以包含多个功能子域。
核心域、支撑域和通用域的主要目标是:通过领域划分,区分不同子域在公司内的不同功能属性和重要性,从而公司可对不同子域采取不同的资源投入和建设策略,其关注度也会不一样。当然我们不能说支撑子域和通用子域是不重要的,它们也是重要的,只是我们对它们的要求并不像核心域那么高。
在这里插入图片描述

2 战术设计

战术设计则从技术视角出发,侧重于领域模型的技术实现,完成软件开发和落地,包括:实体、值对象、聚合根、领域服务、领域事件、应用服务和资源库等代码逻辑的设计和实现。

2.1 实体

定义:在DDD中实体是指具有唯一标识符和生命周期的对象或事物。实体通常具有以下特征:

  • 身份标识;可以用唯一的标识符来区分不同的实体。
  • 生命周期;可能存在创建、修改、删除等状态转换。
  • 属性和行为;属性是实体静态特征和行为是实体的动态特征。
    其中这里唯一标识和可变性将实体和值对象从本质上区分开来。(PS:实体为啥需要唯一标识而值对象不需要?)
    身份标识:有些实体身份标识规定了一些组合规则,如:Citizen实体的身份证号,遵循了一些业务规则,这样的身份蕴含了领域知识,体现了领域概念。
public interface Identity <T>{
    T nextValue();
}

public class UserSignIdGenerator implements Identity<Long> {
    @Autowired
    @Qualifier("userSignUpSequence")
    private GroupSequence userSignUpSequence;

    @Override
    public Long nextValue() {
        return userSignUpSequence.nextValue();
    }

    public long nextValue(long userId) {
        //这里128 是分128张表,分表建按照userId
        return nextValue() *128 +userId % 128;
    }
}

生命周期:比如玩法活动实体->活动开始->活动进行中->活动结束
性和行为:实体的属性用来说明主体的静态特征,根据粒度可以将属性分为原子属性和组合属性(组合属性可以是值对象或者实体)。划分标准是该属性是否存在约束规则、组合因子或者属于自己的领域行为。

public class UserVipAccountDO 

    /**
     * 会员ID
     */
    private Long vipId;

    /**
     * 手机号
     */
    private PhoneNumber phoneNumber;

    
 }

// 比如一个手机号,有业务规则约束,比如11位等,这样的话,设计成PhoneNumber类更合适
// 这里等级其实是组合因子,比如等级名称、描述、升级规则等
public class PhoneNumber {
    private final String number;

    public String getNumber() {
        return number;
    }

    public PhoneNumber(String number) {
        if (number == null) {
            throw new IllegalArgumentException("number不能为空");
        } else if (isValid(number)) {
            throw new IllegalArgumentException("number格式错误");
        }
        this.number = number;
    }
    public static boolean isValid(String number) {
        String pattern = "^0?[1-9]{2,3}-?\\d{8}$";
        return number.matches(pattern);
    }
}

领域行为:实体拥有领域行为,可以更好的说明其作为主体的动态特征;领域行为的方法名应该从业务角度表达领域逻辑。

public class PlayActivityDO extends BaseDO {
    /**
     * 主键-玩法活动id
     */
    private Long playActivityId;
   // .....
    修改的只是对象内存状态和持久化无关
    public void delayActivityTime(Date endTime) {
        // do
    }
}
2.2 值对象

定义:值对象 (Value Object) 是指在软件系统中表示不可变信息的对象。它不具有业务逻辑,也不会改变状态。值对象具有以下特点:

  • 度量或描述:只是用于度量或者描述领域中某件东西的一个概念。比如:一个人拥有年龄、地址等。
  • 不可变性:值对象的值在创建后就不能被修改,任何修改操作都会返回一个新的值对象;
  • 值语义:值对象的相等性是基于其属性值的相等性,而非引用的相等性。
  • 可替换性:值对象可以被其他具有相同属性值的值对象替代,不会对系统的行为产生任何改变。
    值对象也会有一些自给自足的领域行为。如自我验证,自我组合,自我运算。
public class Money {
    private final BigDecimal amount;
    private final Currency currency;

    public BigDecimal getAmount() {
        return amount;
    }

    public Currency getCurrency() {
        return currency;
    }

    public Money(BigDecimal amount, Currency currency) {
        this.amount = amount;
        this.currency = currency;
    }
    //自我组合
    public Money add(Money toAdd) {
        if(!currency.equals(toAdd.currency)) {
            throw new IllegalArgumentException("different currency can not add!");
        }
        return new Money(amount.add(toAdd.amount), currency);
    }
    ....
}
2.3 聚合

定义:聚合是包含了实体和值对象的一个边界,选择一个实体作为这个聚合的根,并允许外部对象仅能持有聚合根的引用。常见误区:

  • 聚合是边界,不是对象,比如玩法活动实体为玩法的聚合,不存在一个玩法活动聚合对象。
  • 将OO关联关系(组合、聚合)和DDD聚合混为一谈
    如下图所UserVIPInfo对象和BenefitRecord对象之间存在1 :n的的聚合关系,但是在DDD设计会把他分配到两个聚合中去。
    在这里插入图片描述
    在这里插入图片描述
    聚合的设计原则:完整性、独立性、不变量和一致性。
    在这里插入图片描述
    完整性:聚合作为一个受到边界控制的领域共同体,对外由聚合根体现为一个统一的概念。如:玩法活动实体。
    独立性:设计小的聚合,应该优保证独立性,再考虑完整性。完整性除了通过聚合来保证,也可以通过聚合之间的关系来保证。
    不变量:在数据变化时必须保持一致性规则,涉及聚合成员之间的内部关系,是约束聚合内对象之间的关系。不变量代表了领域逻辑中的业务规则或者验证条件,有时候可以将不变量理解为不变条件或者固定规则。
    如:要求一个问题必须有四个答案,这种就是业务不变量的约束规则。反例:
    一致性:一致性约束可以理解为事务的一致性,即在事务开始前和事务结束后,数据库的完整性约束没有被破坏。一个聚合必须满足事务的一致性,反之则不尽然.
2.4 领域服务

定义:领域中服务表示一个无状态的操作,它用于实现特定于某个领域任务。当某个操作不适合放到聚合和值对象上时(比如跨聚合的协作),这个时候便可以考虑领域服务了(领域服务是领域建模的最后选择)。领域服务并不映射真实世界的领域概念(名词),而单纯地体现一种领域行为(动词),要求领域服务名称必须包含动词。避免出现:CouponDomainService 这种,建议使用:CouponExchangeDomainService。

2.5 领域事件

定义:领域事件是领域模型的组成部分,表示领域中所发生的事件。一个领域事件将导致进一步的业务操作,在实现业务解耦的同时,还有助于形成完整的业务闭环。如:当什么发生时候…请通知我,当什么完成时候…请通知我。

@Getter
public abstract class DomainEvent {
    //领域事件发生时间
    private final long timeStamp;
    //领域事件Id,本身没有任何业务含义
    private final String eventId;

    public DomainEvent() {
        this.timeStamp = System.currentTimeMillis();
        this.eventId = UUID.randomUUID().toString();
    }
}
@Getter
public class UserSignedEvent extends DomainEvent {
    /**
     * 报名记录Id
     */
    private final Long signRecordId;
    /**
     * 这里活动Id是玩法活动Id
     */
    private final Long activityId;
    /**
     * 报名用户userId,淘系登录
     */
    private final Long userId;

    public UserSignedEvent(Long signRecordId, Long activityId, Long userId) {
        super();
        this.signRecordId = signRecordId;
        this.activityId = activityId;
        this.userId = userId;
    }
}

参考文献
【1】https://time.geekbang.org/column/article/149943
【2】https://martinfowler.com/articles/microservices.html
【3】张逸. 解构领域驱动设计[M]. 北京:人民邮电出版社,2021.09
【4】Vaughn Vernon.实现领域驱动设计[M]. 北京:电子工业出版社出社,2014

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

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

相关文章

马尔可夫链预测 (Markov Chain)

一、人寿保险案例(两状态&#xff09; 人寿保险公司把人的健康状态分为健康和疾病两种&#xff0c;以一年为一个时段&#xff0c;研究健康状态的转变。假设对某一年龄段的人来说&#xff0c;今年健康&#xff0c;明年继续保持健康的概率为0.8&#xff0c;即明年转为疾病的概率…

网络协议定制

目录 一、协议定制 1.再谈协议 2.认识序列化与反序列化 二、网络计算器 1.序列化与反序列化头文件 2.服务端 3.客户端 4.makefile 5.运行结果 三、json序列化与反序列化 1.常用序列化与反序列化库 2.安装 3.使用 &#xff08;1&#xff09;Request的序列化 &…

39.动画动作菜单

特效 源码 index.html <!DOCTYPE html> <html> <head> <title>Animated Action Menu</title> <link rel="stylesheet" type="text/css" href="style.css"> </head> <body><div class=&quo…

功能测试自动化测试流程

1概述 本流程是描述软件功能自动化测试过程中的步骤、内容与方法&#xff0c;明确各阶段的职责、活动与产出物。 2流程活动图 3活动说明 3.1测试计划&#xff08;可选&#xff09; 与以前的测试计划过程一致&#xff0c;只是在原来的测试计划中&#xff0c;添加对项目实施自动…

4、SpringBoot_Mybatis、Druid、Juint整合

五、SSM整合 1.整合Mybatis 1.1springmvc 整合回顾 导入坐标 <dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.2.17.RELEASE</version></dependency><dependency>…

力扣刷题-链表-两两交换链表中的节点

24.两两交换链表中的节点 给定一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后的链表。你不能只是单纯的改变节点内部的值&#xff0c;而是需要实际的进行节点交换。 解题思路 采用正常模拟的方法。 建议使用虚拟头结点&#xff0c;这样会方便很多&am…

大数据从入门到精通(超详细版)之BI工具的安装

前言 嗨&#xff0c;各位小伙伴&#xff0c;恭喜大家学习到这里&#xff0c;不知道关于大数据前面的知识遗忘程度怎么样了&#xff0c;又或者是对大数据后面的知识是否感兴趣&#xff0c;本文是《大数据从入门到精通&#xff08;超详细版&#xff09;》的一部分&#xff0c;小…

iMAP——论文解析

iMAP: Implicit Mapping and Positioning in Real-Time iMAP 是第一个提出用 MLP 作为场景表征的实时 RGB-D SLAM。iMAP 采用关键帧结构和多进程&#xff0c;通过动态信息引导的像素采样来提高速度&#xff0c;跟踪频率为 10 Hz&#xff0c;全局地图更新频率为 2 Hz。隐式 MLP…

Vite打包时使用plugin解决浏览器兼容问题

一、安装Vite插件 在终端输入如下命令&#xff1a; npm add -D vitejs/plugin-legacy 二、配置config文件 在项目目录下创建vite.config.js文件夹&#xff0c;配置如下代码&#xff1a; import { defineConfig } from "vite"; import legacy from "vitejs/pl…

VM虚拟机克隆

VMware 克隆虚拟机具有以下优点&#xff1a; 快速部署&#xff1a;通过克隆虚拟机&#xff0c;可以快速创建新的虚拟机副本&#xff0c;而无需从头开始进行操作系统和应用程序的安装。这节省了大量的时间和工作量。一致性和稳定性&#xff1a;克隆虚拟机是通过复制现有虚拟机来…

DeepMind: 用ReLU取代Softmax可以让Transformer更快

注意力是人类认知功能的重要组成部分&#xff0c;当面对海量的信息时&#xff0c;人类可以在关注一些信息的同时&#xff0c;忽略另一些信息。当计算机使用神经网络来处理大量的输入信息时&#xff0c;也可以借鉴人脑的注意力机制&#xff0c;只选择一些关键的信息输入进行处理…

常见限流算法学习

文章目录 常见限流算法学习前言限流算法基本介绍固定窗口计数器限流算法计数器限流算法相关介绍计数器限流算法的实现&#xff08;基于共享变量&#xff09;计数器限流算法的实现&#xff08;基于Redis&#xff09; 滑动窗口计数器算法滑动时间窗口算法相关介绍介绍滑动时间窗口…

【软件设计师-从小白到大牛】上午题基础篇:第五章 结构化开发方法

文章目录 前言结构化设计1、基本原则真题链接2、内聚与耦合真题链接3、系统结构/模块结构真题链接用户界面设计的黄金原则&#xff08;补充&#xff09;真题链接数据流图&#xff08;补充&#xff09;真题链接系统文档&#xff08;补充&#xff09;真题链接 前言 ​ 本系列文章…

如何使用 Git 进行多人协作开发(全流程图解)

文章目录 分支管理策略1.什么是Feature Branching&#xff1f;2.Feature Branching如何工作&#xff1f; 多人协作一&#xff1a;单分支1.准备工作2.创建分支3.在分支上开发4.分支合并5.清理 多人协作二&#xff1a;多分支1.创建分支2.在分支上开发3. pull request4.清理 在软件…

/usr/bin/ld: cannot find -lmysqlcllient

文章目录 1. question: /usr/bin/ld: cannot find -lmysqlcllient2. solution 1. question: /usr/bin/ld: cannot find -lmysqlcllient 2. solution 在 使用编译命令 -lmysqlclient时&#xff0c;如果提示这个信息。 先确认一下 有没有安装mysql-devel 执行如下命令 yum inst…

js对象属性

在面向对象的语言中有一个标志&#xff0c;那就是都有类&#xff0c;通过类可以创建任意多个相同属性、方法的对象。在js中没有类的存在&#xff0c;所以js中的对象&#xff0c;相对于类语言中对象有所不同。 js中定义对象为&#xff1a;“无序属性的集合&#xff0c;其属性可…

新版绿豆视频APP视频免授权源码 V6.6插件版

新版绿豆视频APP视频免授权源码 V6.6插件版 简介&#xff1a; 新版绿豆视频APP视频免授权源码 插件版 后端插件开源&#xff0c;可直接反编译修改方便 对接苹果cms,自定义DIY页面布局&#xff01; 绿豆影视APP对接苹果cms 所有页面皆可通过后端自由定制 此版本后端源码 前…

二叉树创建、前序遍历、中序遍历、后序遍历、层序遍历

#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<malloc.h> #define N 100 typedef char data_t;typedef struct tree {data_t data;//存放本节点数据struct tree* l_child;//存放左孩子节点地址struct tree* r_child;//存放右孩子节点地址 }Tree;Tre…

Zig实现Hello World

1. 什么是zig 先列出一段官方的介绍: Zig is a general-purpose programming language and toolchain for maintaining robust, optimal, and reusable software. 大概意思就是说&#xff1a; Zig是一种通用编程语言和工具链&#xff0c;用于维护健壮、最佳和可重用的软件。 官…

电脑计算机xinput1_3.dll丢失的解决方法分享,四种修复手段解决问题

日常生活中可能会遇到的问题——xinput1_3.dll丢失的解决方法。我相信&#xff0c;在座的很多朋友都曾遇到过这个问题&#xff0c;那么接下来&#xff0c;我将分享如何解决这个问题的解决方法。 首先&#xff0c;让我们来了解一下xinput1_3.dll文件。xinput1_3.dll是一个动态链…