Go语言DDD实战初级篇

news2024/12/30 2:10:53

在这里插入图片描述

导读

领域驱动设计(DDD)最简洁的描述可能是:如何在明确的限界上下文中创建通用语言的模型。通过 DDD思想设计开发的软件,在领域专家、开发者和软件本身之间不存在“翻译”,三者通过在限界上下文下的通用语言直接表示。而这个系列则是我们团队对 DDD 模式的探索和落地,旨在能帮助大家逐步揭开DDD的神秘面纱。

全文5259字,预计阅读时间14分钟。

一、限界上下文

1.1 前言

DDD分为战略设计和战术设计,战略设计就是划分子域和限界上下文的过程。领域划分为子域的通用划分形式是把领域划分为 核心子域、支撑子域、通用子域。我们在落地过程中常常会很容易划分出核心子域,一般设计mvp的时候mvp就是核心子域。但是领域划分出核心子域、支撑子域和通用子域之后就算划分完成了吗?

1.2 子域和限界上下文

实际上子域也是领域,一个公司不同部门关注的是一个大领域的不同子领域,在你关注的领域内也需要做这种子域的划分。

比如百度这个大公司,有很多部门,这些部门都属于互联网领域,但是每个部门又有自己关注的领域,比如游戏部门关注的是游戏领域、搜索部门关注的是搜索领域。

不同部门的领域还可以再继续划分出自己关注的领域的核心域和支撑子域,所以整体上,领域的划分就像一棵树。我们回到自己关注的领域,基于这个领域做划分。我们会把这个关注的领域划分为核心域、支撑域和通用域,一般每个域都由一个小团队负责(康威定律)。

如果一个团队的工作是支撑域,那么这个支撑域就是他们的核心域,他们可以对此再做细致的划分,何时划分到头呢?用一个具体的限界上下文解决这个叶子领域的所有问题,并且领域通用语言在这个上下文中没有二义性,那么就算划分到头了。

图片

划分到树叶的领域都是待解决的问题,也叫问题域,而限界上下文呢就是用来解决这个域内所有问题的模型。

针对限界上下文与领域的对应关系Vernon给出了建议,最好是1:1的关系,当然也有其他说法如1:N,N:N,本人认同Vernon的说法,如果子域对应多个限界上下文,那么只能说该子域还可以再划分为子子域,由子子域去对应每个限界上下文。划分好子域和限界上下文后,限界上下文的主题就是解决这个子领域的问题,手段就是DDD战术建模,工具就是领域通用语言,限制就是领域通用语言不能有二义性。

1.3 划分领域(限界上下文)的依据

  • 通用语言:在做领域划分之前一定要统一领域通用语言,如果一个名词在用语言描述的时候在不同语境有不同含义,那么就应该在不同语境中创建不同上下文。比如book,在写作阶段就是草稿,在出版阶段就是一个出版物,在购买阶段是一个书籍类商品,在发货阶段是一个物流订单。那么就应该按照书的角色进行归类,区分出上下文。正所谓,在商言商,在领域就应该说领域的通用语言。

  • 领域职责:不同领域想要达成的目标是不一样的,每个领域都有自己最终要完成的事情,即通过领域知识,完成领域活动,最后完成领域职责。

  • 领域角色:不同领域的角色也不尽相同,前端领域里可能需要ue角色、fe角色。后端领域里可能需要java研发、dba等角色。同时上边举的book的例子,book在不同领域的角色也是不一样的。再通俗一点,你在学校是学生角色,上班是员工角色。

  • 领域知识:不同领域包括的知识是不一样的,比如后端和前端,后端可能需要了解服务器相关的知识、前端需要了解的是界面相关的知识。

  • 领域活动:不同领域的职责也是不同的,在领域内进行的活动也不一样,比如前端需要构建前端页面活动。后端处理数据库交互活动。领域活动会利用到领域知识,如果进行领域活动的时候却不具备这个领域的知识,那么说明领域划分是不合理的。

  • 领域关注点:不同领域关注点不同,拿person举例,person有身份证信息、年龄、身高、体重、工作、专业等信息,但是在不同领域对person的关注点是不一样的,银行办信用卡不需要身高体重信息、参加奥运会却关注身高体重,相亲时不会关注身份证信息,但却关注你的工作、年龄等。

1.4 落地经验

在落地过程中我们遇到了一个建模问题:

我们的服务有两个角色使用:

  • 运营人员:运营人员要配置模型的各种规则,但是频次相对较低。

  • 用户:用户会使用运营人员配置的规则,使用频次较高。

在项目初期由于设计问题,最终放弃了拆分这两个上下文,而是使用相同的上下文进行了建模。

这个问题的本质是我们没有想好领域划分,现在回头想想,我们处理的是一个核心域,但是这个核心域又可以分为两个子域:一个是配置平台子域,一个是用户使用子域。

两个子域的关注点是不同的,并且变化频次也不同,后续用户使用上下文会做横向扩展,我们目前的单体架构虽然能做扩展,但是不符合单一职责原则,因为用户使用平台集成了配置功能,而配置功能是不应该随着用户功能进行扩展的。在拆分过程中,会有很多代码是重叠的,我们的服务中就有很多Aggregate聚合,在两个上下文中有很多字段是一样的,但重复并不一定是错误,因为重复的代码关注点和变化频率是不一样的。这里我们介绍了利用角色进行关注点区分,进而划分子域和限界上下文的方法,实际上也可以根据其他条件对领域进行划分,划分只要保证概念相对独立,关注点相对独立,划分后没有丢失问题就可以。

1.5 小结

  1. 领域就是有一个范围,在这个范围内有不同的角色,每个角色都有该角色应该具备的领域知识,各角色之间通过自己掌握的知识完成彼此协作,完成一些领域活动,产生一些领域事件,最终完成领域职责。
  2. 划分领域的依据就是领域职责(目标)、领域关注点、完成职责需要的角色、角色需要的知识、角色需要执行的活动。
  3. 事件风暴的过程也是识别领域活动、领域职责、领域角色、领域事件、领域知识的过程。

二、实体

2.1 前言

实体是领域驱动设计中非常重要的一个部分,Len Silerston 说:“实体是一个重要的概念,企业希望建立和存储的信息都是关于实体的信息”。在 DDD 中,实体的构建是重中之重。

2.2 什么是实体

实体,是谓词描述的主体。它包含了其他范畴,如引起属性变化和状态迁移的动作。一个典型的实体应该具有3个要素:

  • 身份标识

    • 通用类型:ID值没有业务含义,唯一即可。
    • 领域类型:通常与各个界限上下文的实体对象有关。
  • 属性:说明主体的静态特征,并持有数据与状态。可以划分为原子属性和组合属性。划分的依据是:该属性是否存在约束规则、组合因子或属于自己的领域行为。

    • 原子属性:
      • name
    • 组合属性:
      • price(num, unit);
      • 组合属性是一种很好的特质,当一个实体有了一些组合属性后, 一些细小的概念 对应的职责(基本校验、计算)将由各自的属性进行负责,而实体更关注自身概念。
  • 领域行为

    • 变更状态的领域行为:实体对象是允许调用者改变状态的,这样就产生了变更状态的领域行为(方法名上不建议用set/get, 而是更具有业务含义的方法名,这样更具有领域逻辑(加强))
    • 自给自足的领域行为:对象只操作了自己的属性,而不依赖于外部属性。(如校验一份外卖的总金额、总数量 与外卖中各个单品的关系)
    • 互为协作的领域行为:需要调用者提供必要的信息(一般通过方法参数传入),这样就形成了领域对象之间互为协作的领域行为。
      增删改查。

2.3 构建实体的依据

在 DDD 设计中,我们将开发者的视线从数据库移到了实体上,以往我们在设计一个系统时,会关注要建立多少张表,而我们在 DDD 中,则需要关注如何建立实体,这两者的异同点在于:

  • 相同:在 mvc 的开发模式中,开发者通过阅读dao 层的表结构,就能了解到整个系统大致的架构与作用。同样的,在 DDD 中开发者通过阅读实体的属性,就能了解到整个系统大致的架构与作用。
  • 不同:在 DDD 中,一个实体的属性可能只由一张表组成,也可能由多张表组成,也可能是由mysql 和 redis 共同组成,在实体所在的领域中,我们并不关心它的底层(数据层)是如何实现的,我们只关心这个实体。

举个例子

对于一个学生信息管理系统而言,我们设计了一个学生的实体。

type Student struct {
        ID     uint64
        Name   string
        Sex    string
        Class  string
        IsLate int
        Sign   *Sign
}
type Sign struct {
        SignTime time.Time
}

func (stu *Student) StudentSign() {
        isLate := TimeCheck()
        stu.IsLate = isLate
        // flush redis...
}

以上实体的结构可以简单概括为:

  1. 身份标识:ID
  2. 属性:Name、Sex、Class、IsLate、值对象(Sign)
  3. 领域行为:Sign()

在我们的数据库设计中,Student 的基础信息,可能只包括了ID、Name、Sex、Class 这四个字段,那IsLate 字段呢?我们将学生 IsLate 属性写进缓存里,方便某些监察管理系统的高频查询,同时我们通过 Sign()方法进行学生签到状态的变更,我们在 Sign 方法中进行校验后,修改这个学生实体的 IsLate 属性。

补充:

值对象也是实体对象的属性之一,它没有身份标识,也不可改变。比如上面的签到,学生在今天签到之后,创建的签到记录,就是学生的值对象,这条记录创建了,就不可改变了(排除黑入教务系统篡改个人数据的情况)。值对象更多的信息,会在后面提到。

三、值对象

3.1 前言

值对象是实体的一个重要组成部分,如何正确使用值对象,也是 DDD领域驱动设计的一个难题。本文将介绍值对象的概念与使用方法。

3.2 概念

值对象是实体对象的属性,通常代表分量、性质、关系、场所、时间或位置/姿态。当实体属性需要表现出其属性的意义,并为这个意义提供相关功能,可以设置为值对象。比如一家公司所在的省/市/区/街道可以合成值对象表示这家公司的地址属性。

3.3 特点

  • 值对象不可变。建模优先考虑值对象,因为值对象没有身份表示的负担,本身不可变。值对象本身最多是不可变性。
  • 值对象拥有的往往是“自给自足的领域行为”。这些领域行为能够让值对象的表现能力变得更加丰富,更加智能。

3.4 领域行为

那什么是值对象的领域行为呢?

  • 自我验证:值对象自我组合,能减少实体类的验证。
  • 自我组合: 值对象会涉及对数据值的运算,为了增强值对象的运算能力,可以在内部进行数据组合。如 金额的单位换算。
  • 自我运算: 按照业务规则对属性值运算的行为。如经纬度计算。
// NewCoordinateVo 初始化坐标值对象
func NewCoordinateVo(LongitudeStr string, LatitudeStr string) (*VoCoordinate, error) {
  // 自我验证
  Longitude, err := strconv.ParseFloat(LongitudeStr, 64)
  if err != nil {
    return nil, fmt.Errorf("Longitude_input_err")
  }
  Latitude, err := strconv.ParseFloat(LatitudeStr, 64)
  if err != nil {
    return nil, fmt.Errorf("Latitude_input_err")
  }
  return &VoCoordinate{
    Longitude: Longitude,
    Latitude:  Latitude,
  }, nil
}

3.5 F&Q

1、相比于普通属性,值对象有哪些优势呢?

可以展现领域概念;学生实体的年龄,string与Name、int与Age相比,显然后者更加直观得体现了业务含义。可以封装显而易见的领域概念;比如对于一个经销商4s店店位置经度和纬度都是这个4s店实体实体店属性,但是合成一个坐标值对象更能展示实体店领域概念。更好的封装利于自我领域行为的验证能力。保证每次生成得值对象都是正确的。

2. 那么一个领域的概念我们用实体还是值对象呢?可以依据几点来判断?

业务对它相等的判断是根据值还是身份标识。前者是值对象,后者是实体。当我们从图书馆判断一本书是否相同,即使名字相同也并非同一本书,在系统中,只有id相同才是同一本书;但我们判断一个位置,当经纬度相同的时候就是同一个位置。这个时候图书就是定义为实体,坐标定义为值对象。

确定对象的属性值是否会发生变化,如果变化了,究竟是产生一个完全不同的对象,还是维持相同的身份标识。在员工的出勤记录业务场景中,依据相等性进行判断时,可以任务出勤记录值相等的就是同一条记录,但如果员工提出补卡,对记录状态修改对时候,其同一性就只能通过唯一的身份标识进行判断,这意味这应该被定义为实体。

生命周期是手动的。值对象没有身份标识,意味着无需管理其生命周期。但是实体无需关注。

多个判断条件是层层递进的,要确定一个领域概念究竟是实体还是值对象,需要谨慎判断,综合考量。

—— END——

推荐阅读

百度工程师带你玩转正则

Diffie-Hellman密钥协商算法探究

贴吧低代码高性能规则引擎设计

浅谈权限系统在多利熊业务应用

分布式系统关键路径延迟分析实践

百度工程师教你玩转设计模式(装饰器模式)

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

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

相关文章

交换机——VLAN原理和基本配置

VLAN原理和配置VLAN的三种端口类型Access:接入链路类型(一般使用:交换机与PC)Trunk:干道链路类型(一般使用:交换机与交换机)Hybrid在这里,我们只使用和讲解Access和Trunk…

HTML简介

目录 一、HTML基础知识 二、HTML常见标签 注释标签 标题标签 段落标签 常用的转义字符 换行标签 格式化标签 图片标签 超链接标签 表格标签 列表标签 input标签 文本框 密码框 单选框 复选框 普通按钮 选择文件 下拉标签 多行文本输入 无语…

【哈希表】leetcode454.四数相加II(C/C++/Java/Python/Js)

leetcode454.四数相加II1 题目2 思路3 代码3.1 C版本3.2 C版本3.3 Java版本3.4 Python版本3.5 JavaScript版本4 总结需要哈希的地方都能找到map的身影 1 题目 题源链接 给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少…

C中线程信号控制

一、场景介绍存在三个线程,一个主线程和两个子线程(子线程数量不固定)。为了节省频繁创建销毁线程造成的资源浪费,将这些线程设置为常驻线程。但这样引入了一个新的问题,如何协调这些线程完成工作。主线程内是循环检测…

Webgl实现的天气效果(下雨、下雪)

一、下雨效果如图: 还有一种雨水效果也不错 114 Three.js实现深度遮挡的下雨特效 | 暮志未晚-中文案例网 二、下雪的效果 57 Three.js 使用粒子实现下雪特效 | 暮志未晚-中文案例网 还有一种是通过网页CSS的形式做的2D雪效果 集合在一起的源码: https…

编译原理学习笔记17——语义分析和中间代码生成2

编译原理学习笔记17——语义分析和中间代码生成217.1 赋值语句的翻译17.2数组元素引用的翻译17.3 类型转换17.1 赋值语句的翻译 简单算术表达式及赋值语句 赋值语句生成三地址代码的S-属性文法 赋值语句生成三地址代码的S-属性文法 产生赋值语句三地址代码的翻译模式 产…

你真的了解工厂设计模式吗?(简单工厂模式+工厂方法模式+抽象工厂模式)

工厂解决的问题 客户端在调用是不想判断实例化哪一个类或者实例化的过程过于复杂。在工厂模式中,具体的实现类创建过程对客户端是透明的,客户端不决定具体实例化哪一个类,而是交由“工厂”来实例化。 简单工厂模式 类图 简单工厂模式由三类…

Git GitHub纯新手入门教程

参考视频:Github 新手够用指南 | 全程演示&个人找项目技巧放送_哔哩哔哩_bilibili40 分钟学会 Git | 日常开发全程大放送&个搭配GitHub_哔哩哔哩_bilibiliGit和GitHub分别是什么Git是一个运行在电脑上的版本控制软件(保存代码各个阶段历史记录的…

在使用定时器过程中存在的那些陷阱

在使用定时器的过程中,如果你不了解定时器的一些细节,那么很有可能掉进定时器的一些陷阱里,函数 setTimeout 在时效性上面有很多先天的不足,所以对于一些时间精度要求比较高的需求,应该有针对性地采取一些其他的方案 …

【回眸】牛客网刷刷刷(四)软件工程(续)ZooKeeper字符串链表(专题)

前言 本篇博客为笔者刷客观笔试题时做的一些记录以供以后复习时翻阅,如果能够帮到您是最大的荣幸!如果能给笔者一个三连将感激不尽! 知识点串烧 软件工程专题(续上篇) 有一些可维护特性是相互促进的,如…

【技术美术图形部分】PBR Disney原则的BRDF 次表面散射模型

写在前面 补充去年遗漏下的知识。很多叙述都是参考了众多大佬的文章!因为是作为个人学习总结的博客,所以直接卑微的借鉴过来了,后面会给出所有参考的文章。 另外,放上一个忘了在哪一篇知乎评论里的截图: 说的蛮好。 …

MySQL基础篇笔记

文章目录导入表的问题第3章_最基本的SELECT语句1. SQL语言的规则和规范1) 基本规则2) SQL大小写规范(建议遵守)3) 注释4) 命名规则2. 基本的SELECT语句1) SELECT ... FROM2) 列的别名3) 去除重复行4) 空值参与运算5) 着重号 6) 查询常数3. 显示表结构4. …

贪心算法(基础)

目录 一、什么是贪心? (一)以教室调度问题为例 1. 问题 2. 具体做法如下 3. 因此将在这间教室上如下三堂课 4. 结论 (二)贪心算法介绍 1. 贪心算法一般解题步骤 二、最优装载问题 (一&#xf…

智能驾驶 车牌检测和识别(四)《Android实现车牌检测和识别(可实时车牌识别)》

智能驾驶 车牌检测和识别(四)《Android实现车牌检测和识别(可实时车牌识别)》 目录 智能驾驶 车牌检测和识别(四)《Android实现车牌检测和识别(可实时车牌识别)》 1. 前言 2. 车…

SLAM数学知识回顾

文章目录1、三角函数2、向量运算(1)负向量(2)向量的模(3)标量与向量的运算(4)标准化向量(5)向量的加法和减法(6)距离公式(…

三十七、Kubernetes1.25中数据存储第三篇

1、概述在前面已经提到,容器的生命周期可能很短,会被频繁地创建和销毁。那么容器在销毁时,保存在容器中的数据也会被清除。这种结果对用户来说,在某些情况下是不乐意看到的。为了持久化保存容器的数据,kubernetes引入了…

计算机视觉OpenCv学习系列:第十部分、实时人脸检测

第十部分、实时人脸检测第一节、实时人脸检测1.OpenCV人脸检测支持演化2.OpenCV DNN检测函数3.代码练习与测试学习参考第一节、实时人脸检测 1.OpenCV人脸检测支持演化 OpenCV4 DNN模块 DNN- 深度神经网络 来自另外一个开源项目tiny dnnOpenCV3.3正式发布最新版本OpenCV4.5.…

网络编程 之 epoll

epoll 参数设置 events设置 ev.events EPOLLIN | EPOLLET;epoll实现TCP通讯时,events通用设置如上,EPOLLIN代表可socket套接字可接收数据,EPOLLET代表边沿触发。在服务器端, 接受客户端连接的socket不能设置为EPOLLOUT,只设置E…

【5】【TypeScript】(TypeScript=Type+JavaScript)

Typescript 相比js特有 类型系统;对象的接口DOM操作时候需要进行类型断言上面三个实际是类型系统的三处体现枚举js中,-号可以强制转换为数值,ts不行 所有合法的js都是ts 1、安装 安装进度卡住可以用淘宝镜像 (在后面加 --registr…

Spring Cloud Hystrix有什么作用?

在微服务架构中,通常会存在多个服务层调用的情况,如果基础服务出现故障可能会发生级联传递,导致整个服务链上的服务不可用,如图1所示。图1 服务故障的级联传递在图1中,A为服务提供者,B为A的服务调用者&…