【DDD】学习笔记-领域实现模型

news2024/9/23 3:27:54

实现模型与编码质量

领域设计模型体现了类的静态结构与动态协作,领域实现模型则进一步把领域知识与技术实现连接起来,但同时它必须守住二者之间的边界,保证业务与技术彼此隔离。这条边界线应由设计模型明确给出,其中的关键是遵循整洁架构、六边形架构与分层架构,做好基础设施层实现机制的抽象,即我在[《领域驱动设计实践(战略篇)》]中提到的“南向网关”的内容。这正好说明了领域分析模型、领域设计模型与领域实现模型之间的统一关系,前者往往会成为后者的基础。

我认为,测试驱动开发可以很好地满足将领域设计模型转换为领域实现模型的需求。注意,测试驱动开发并不等于是“测试先行”,也不能简单地将其视为一种编程手段。我理解的测试驱动开发(Test Driven Development,TDD)包含两个 TDD 阶段:

  • 第一个阶段是任务分解驱动设计(Tasking Driven Design):通过对用户故事进行任务分解,可以降低需求复杂度。这一过程恰好与职责驱动设计中对职责的分解相对应,实际上都是一种“分而治之”的思想。每个分解的任务或子任务皆以动宾短语的形式表达,这就相当于寻找到了各个需要履行的职责,以及履行职责的时序。因此,设计模型中的时序图可以作为测试驱动开发的重要输入。
  • 第二个阶段是测试驱动开发:依照事先拆分好的任务,进一步结合业务场景将任务划分为多个可以验证的测试用例,然后开始编写测试,并按照红—绿—重构的节奏开始编码实现。

分解的任务是有层次的,大致可以划分为业务价值、业务功能与业务实现三个层次。这三个层次还可以进一步递归分解,这取决于业务场景的粒度。在选择测试要驱动的任务时,可以采用自外向内或自内向外这两种不同的实现方向。在建立领域设计模型时,我们往往会采用自外向内的方向,这其实就是前面讲解的服务模型驱动设计的设计方向,符合“意图导向编程”的思想。而在选择测试用例时,则应该反其道而行之,从最小粒度的原子任务开始,这样在一定程度上能减少不必要的 Mock 协作,也能够减少最外层服务因为分支覆盖率的原因带来的测试用例组合爆炸:

79098254.png

测试驱动开发严格遵循 Kent Beck 提出的简单设计原则,内容为:

  • 通过所有测试(Passes its tests)
  • 尽可能消除重复 (Minimizes duplication)
  • 尽可能清晰表达 (Maximizes clarity)
  • 更少代码元素 (Has fewer elements)
  • 以上四个原则的重要程度依次降低

“通过所有测试”原则意味着我们开发的功能满足客户的需求,这是简单设计的底线原则。该原则同时隐含地告知与客户或领域专家(需求分析师)充分沟通的重要性。

“尽可能消除重复”原则是对代码质量提出的要求,并通过测试驱动开发的重构环节来完成。注意此原则提到的是 Minimizes(尽可能消除),而非 No duplication(无重复),因为追求极致的重用存在设计与编码的代价。

“尽可能清晰表达”原则要求代码要简洁而清晰地传递领域知识,在领域驱动设计的语境下,就是要遵循统一语言,提高代码的可读性,满足业务人员与开发人员的交流目的。针对核心领域,甚至可以考虑引入领域特定语言(Domain Specific Language,DSL)来表现领域逻辑。

在满足这三个原则的基础上,“更少代码元素”原则告诫我们遏制过度设计的贪心,做到设计的恰如其分,即在满足客户需求的基础上,只要代码已经做到了最少重复与清晰表达,就不要再进一步拆分或提取类、方法和变量。

这四个原则是依次递进的,功能正确、减少重复、代码可读是简单设计的根本要求。一旦满足这些要求,就不能创建更多的代码元素去迎合未来可能并不存在的变化,避免过度设计。当简单设计原则与测试驱动开发结合起来之后,测试保证了功能的正确性,重构则保证了代码的质量。由于有大量的测试保护,即使未来发生了变化,也能让开发人员在调整代码结构应对变化时充满信心。测试、实现与重构共同构成了测试驱动开发的核心:

32822541.png

图片来源于网络

重构既可以让领域实现模型满足统一语言的要求,并帮助我们发现隐含概念,又可以让我们的面向对象设计做得更好。玉不琢不成器,代码也需要不断地打磨,这个过程就是对代码坏味道的识别与消除,进而在重构的过程中,逐渐让我们的实现向着面向对象设计的范式靠拢。

例如,通过识别出“依恋情节(Feature Envy)”的坏味道,就可以结合提取方法(Extract Method)与移动方法(Move Method)等重构手法,将行为转移到拥有数据的模型对象上,避免了贫血模型。又例如识别出“过长参数列表(Long Parameter List)”的坏味道,就可以通过引入参数对象(Introduce Parameter Object)重构手法,获得在分析建模与设计建模中未曾发现的隐式领域概念。

在通过单元测试进行测试驱动开发时,我们强调单元测试的快速反馈。对于单元测试的定义,Michael Feathers 认为:运行快、不依赖于任何外部资源的测试就是单元测试。因此,如下所述的测试并非单元测试:

  • 和数据库有交互
  • 进行了网络间通信
  • 调用了文件系统
  • 需要你对环境做特定的准备(如编辑配置文件)才能运行的

这些职责恰好属于业务逻辑需要调用的所谓“南向网关”的部分,被放在整洁架构的最外侧一环,如下图所示的 DB、Devices 与 External Interfaces:

35360091.png

图片来源于网络

遵循整洁架构思想与依赖倒置原则(DIP),我们需要对这些职责进行抽象。该抽象正好对应于领域设计模型中的 Gateway 角色,即对“访问外部资源”行为的封装与抽象。在测试驱动开发中,这些职责可以利用类似 Mockito 这样的模拟框架对其进行模拟,使得我们在编写测试时,可以仅关注具体的业务逻辑,而忽略与外部资源的协作。这既符合测试驱动开发的原则,又能满足领域驱动设计将设计重心放在“领域”的要求,自然而然地做到了业务复杂度与技术复杂度的隔离。

运用测试驱动开发编写的测试代码也是组成领域实现模型的关键部分。前面提到,在测试驱动开发阶段,应根据“事先拆分好的任务,进一步结合业务场景将任务划分为多个可以验证的测试用例”,因此,这些测试用例都体现了具体的业务场景。我们的实践是以接近自然语言的形式定义测试方法,例如针对转账业务场景,划分的测试用例对应的测试方法可以定义为:

public class AccountTest {
    @Test
    public void should_report_InsufficientFundsException_given_not_enough_balance_of_source_account() {}

    @Test
    public void should_report_InvalidAccountException_given_invalid_destination_account() {}

    @Test
    public void should_transfer_from_src_account_to_dest_account_given_correct_transfer_amount() {}
}

每个测试方法只能做一件事情,而且每个测试方法都是独立的。

在编写测试方法时,还应遵循 Given-When-Then 模式。这种编写模式描述了测试的准备、期待的行为,以及相关的验收条件:

  • Given:为要测试的方法提供准备,包括创建被测试对象,为调用方法准备输入参数实参等。
  • When:调用被测试的方法,遵循 SRP 原则,在一个测试方法的 when 部分,应该只有一条语句对被测方法进行调用。
  • Then:对调用后的结果进行预期验证。

例如:

@Test
public void should_transfer_from_src_account_to_dest_account_given_correct_transfer_amount() {
    // given
    Money balanceOfSrc = new Money(100_000L, Currency.RMB);
    SourceAccount src = new Account(srcAccountId, balanceOfSrc);

    Money balanceOfDes = new Money(0L, Currency.RMB);
    DestinationAccount dest = new Account(destAccountId, balanceOfDes);

    Money trasferAmount = new Money(10_000L, Currency.RMB);

    // when
    src.transferMoneyTo(dest, transferAmount);

    // then
    assertThat(src.getBalance().getFaceValue()).isEqualTo(90_000L);
    assertThat(dest.getBalance().getFaceValue()).isEqualTo(10_000L);
}

这样的测试代码体现了领域逻辑,可以认为是领域实现模型的一部分。倘若在实现过程中,还能够结合规格说明(Specification)风格的验收测试,通过接近自然语言的领域特定语言编写测试用例场景,将用户故事、代码实现与测试用例三者结合起来,形成所谓的“活文档(Live Document)”。这样的活文档既能够促进团队与领域专家的沟通,又能真实地体现实现逻辑,是领域建模的重要实践,输出的同样是领域实现模型的一部分。

领域模型驱动设计的过程

整体而言,在领域模型驱动设计的语境下,领域分析模型从业务系统中抽象出核心的领域概念,与领域专家一起获得领域见解,并提炼出有价值的领域知识,从而建立一个有利于与领域专家沟通的抽象模型。领域分析模型与任何软件开发技术都没有关系,只取决于团队对领域知识的理解。

领域设计模型则是在领域分析模型基础上的技术演进,例如对领域分析模型中的领域对象进行职责分配,建立抽象接口完成模块以及对象之间的解耦,对代表领域概念的类进行更合理的封装,隐藏不必要的细节,并对领域分析模型中的领域对象运用 Eric Evans 提出的设计要素与模式。

领域实现模型提供遵循领域设计模型的编程实现,这时需要考虑具体的实现机制,但同时又必须保持业务复杂度与技术复杂度的分离,避免出现复杂度的叠加效应。当然,实现模型总是由编程语言来表示,不同语言有不同的惯用法、不同的语法糖,即使在相同语言下,选择不同的框架,由于框架的设计原则和思路亦有所不同,导致实现模型会有所区别。整个领域模型驱动设计的过程如下图所示:

50470213.png

在领域模型驱动设计过程中,是领域分析模型、领域设计模型与领域实现模型共同构成了领域模型,因此这里列出的三个模型并不是独立无关的,与之对应的建模活动也不是独立无关的。这三个模型是统一的整体,只是在不同的阶段需要有不同的分析建模方法,又因为交流的对象不同,需要有不同的模型呈现形式。因此,要掌握领域驱动设计,在战术设计层面就必须要理解什么才是真正的领域模型。

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

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

相关文章

基于SSM的网络在线考试系统(有报告)。Javaee项目。ssm项目。

演示视频: 基于SSM的网络在线考试系统(有报告)。Javaee项目。ssm项目。 项目介绍: 采用M(model)V(view)C(controller)三层体系结构,通过Spring …

数字图像处理实验记录十(图像分割实验)

一、基础知识 1、什么是图像分割 图像分割就是指把图像分成各具特性的区域并提取出感兴趣目标的技术和过程,特性可以是灰度、颜色、纹理等,目标可以对应单个区域,也可以对应多个区域。 2、图像分割是怎么实现的 图像分割算法基于像素值的不连…

Java微服务学习Day1

文章目录 认识微服务服务拆分及远程调用服务拆分服务远程调用提供者与消费者 Eureka注册中心介绍构建EurekaServer注册user-serviceorder-service完成服务拉取 Ribbon负载均衡介绍原理策略饥饿加载 Nacos注册中心介绍配置分级存储负载均衡环境隔离nacos注册中心原理 认识微服务…

《剑指 Offer》专项突破版 - 面试题 30 和 31:详解如何设计哈希表以及利用哈希表设计更加高级、复杂的数据结构

目录 一、哈希表的基础知识 二、哈希表的设计 2.1 - 插入、删除和随机访问都是 O(1) 的容器 2.2 - 最近最少使用缓存 一、哈希表的基础知识 哈希表是一种常见的数据结构,在解决算法面试题的时候经常需要用到哈希表。哈希表最大的优点是高效,在哈希表…

java实现算法

一、二分法 二分法查找主要是为了快速查找给定数组内,期待值在数组中的位置(下标) 二分法查找通过对整个数组取中间值,判断期待值所在的范围并缩小范围,每次查找范围折半,直到范围的边界重合,…

终端命令提示符:如何查看我们电脑端口是否被占用和处理方式

文章目录 端口信息查看1、Windows:2、Linux/macOS: 使用 netstat使用 lsof 端口信息查看 在不同的操作系统中,查看端口是否被占用的指令有所不同。以下是一些常见的指令: 1、Windows: 使用命令行工具 netstat 来查看端口占用情况。 电脑键盘按住 win…

第九个知识点:内部对象

Date对象: <script>var date new Date();date.getFullYear();//年date.getMonth();//月date.getDate();//日date.getDay();//星期几date.getHours();//时date.getMinutes();//分date.getSeconds();//秒date.getTime();//获取时间戳&#xff0c;时间戳时全球统一&#x…

[计算机提升] 备份系统:系统映像

6.3 备份系统&#xff1a;系统映像 备份系统和还原系统是一套互补的操作。 操作系统的备份就是将操作系统当前的所有数据复制到硬盘的一个空闲区域&#xff0c;以防止系统崩溃或数据丢失。还原操作则是将先前备份的数据恢复到操作系统中&#xff0c;使系统回到之前的样子&…

Python进程之串行与并行

串行和并行 串行指的是任务的执行方式。串行在执行多个任务时&#xff0c;各个任务按顺序执行&#xff0c;完成一个之后才能进行下一个。&#xff08;早期单核CPU的情况下&#xff09; 并行指的是多个任务在同一时刻可以同时执行&#xff08;前提是多核CPU&#xff09;&#…

蓝桥杯备赛Day9——链表进阶

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。 示例 1: 输入:head = [1,2,3,4,5], n = 2 输出:[1,2,3,5]示例 2: 输入:head = [1], n = 1 输出:[]示例 3: 输入:head = [1,2], n = 1 输出:[1]提示: 链表中结点的数目为 sz1 <= sz <= 300 &l…

2024-02-07(Sqoop,Flume)

1.Sqoop的增量导入 实际工作中&#xff0c;数据的导入很多时候只需要导入增量的数据&#xff0c;并不需要将表中的数据每次都全部导入到hive或者hdfs中&#xff0c;因为这样会造成数据重复问题。 增量导入就是仅导入新添加到表中的行的技术。 sqoop支持两种模式的增量导入&a…

sqli.labs靶场(41-53关)

41、第四十一关 -1 union select 1,2,3-- -1 union select 1,database(),(select group_concat(table_name) from information_schema.tables where table_schemadatabase()) -- -1 union select 1,2,(select group_concat(column_name) from information_schema.columns wher…

2024年【天津市安全员B证】模拟试题及天津市安全员B证模拟考试题库

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 天津市安全员B证模拟试题是安全生产模拟考试一点通生成的&#xff0c;天津市安全员B证证模拟考试题库是根据天津市安全员B证最新版教材汇编出天津市安全员B证仿真模拟考试。2024年【天津市安全员B证】模拟试题及天津市…

零基础学Python之Unitest模块

1.unittest简介及入门案例 &#xff08;1&#xff09;什么是Unitest Unittest是Python自带的单元测试框架&#xff0c;不仅适用于单元测试&#xff0c;还可用于Web、Appium、接口自动化测试用例的开发与执行。该测试框架可组织执行测试用例&#xff0c;并且提供丰富的断言方法…

如何使用CLZero对HTTP1.1的请求走私攻击向量进行模糊测试

关于CLZero CLZero是一款功能强大的模糊测试工具&#xff0c;该工具可以帮助广大研究人员针对HTTP/1.1 CL.0的请求走私攻击向量进行模糊测试。 工具结构 clzero.py - 工具主脚本&#xff1b; default.py - 包含了大多数标准攻击测试方法和字符&#xff1b; exhaustive.py - 包…

Git介绍和常用命令说明

目录 一、Git概述 1.1 Git是什么 1.2 Git有什么用 1.3 Git仓库介绍 二、Git下载与安装 三、Git代码托管服务&#xff08;远程仓库&#xff09; 四、Git常用命令 4.1 设置用户信息 4.2 获取Git仓库 4.2.1 本地初始化Git仓库 4.2.2 从远程仓库克隆 4.3 本地仓库操作 …

navigator.mediaDevices.getUserMedia获取本地音频/麦克权限并提示用户

navigator.mediaDevices.getUserMedia获取本地音频/麦克权限并提示用户 效果获取权限NotFoundErrorNotAllowedError 代码 效果 获取权限 NotFoundError NotAllowedError 代码 // 调用 captureLocalMedia()// 方法 function captureLocalMedia() {console.warn(Requesting lo…

Mac 下JDK环境变量配置 及 JDK多版本切换

一、推荐官网下载&#xff1a; 二、环境变量配置 1、查看JDK地址&#xff0c;在终端输入以下命令&#xff1a; /usr/libexec/java_home -V 我的路径&#xff1a; /Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home /Library/Java/JavaVirtualMachines/zulu-11.j…

MGIE官网体验入口 苹果多模态大语言模型AI图像编辑工具在线使用地址

MGIE是一项由苹果开源的技术&#xff0c;利用多模态大型语言模型&#xff08;MLLMs&#xff09;生成图像编辑指令&#xff0c;通过端到端训练&#xff0c;捕捉视觉想象力并执行图像处理操作&#xff0c;使图像编辑更加智能、直观。 MGIE官网体验入口https://github.com/apple/M…

Python进行AI声音克隆的端到端指南

人工智能语音克隆是一种捕捉声音的独特特征&#xff0c;然后准确性复制它的技术。这种技术不仅可以让我们复制现有的声音&#xff0c;还可以创造全新的声音。它是一种彻底改变内容创作的工具&#xff0c;从个性化歌曲到自定义画外音&#xff0c;开辟了一个超越语言和文化障碍的…