【DDD】学习笔记-建立统一语言

统一语言是提炼领域知识的产出物,获得统一语言就是需求分析的过程,也是团队中各个角色就系统目标、范围与具体功能达成一致的过程。

使用统一语言可以帮助我们将参与讨论的客户、领域专家与开发团队拉到同一个维度空间进行讨论,若没有达成这种一致性,那就是鸡同鸭讲,毫无沟通效率,相反还可能造成误解。因此,在沟通需求时,团队中的每个人都应使用统一语言进行交流

一旦确定了统一语言,无论是与领域专家的讨论,还是最终的实现代码,都可以通过使用相同的术语,清晰准确地定义领域知识。重要的是,当我们建立了符合整个团队皆认同的一套统一语言后,就可以在此基础上寻找正确的领域概念,为建立领域模型提供重要参考。

统一语言体现在两个方面:

  • 统一的领域术语
  • 领域行为描述

统一的领域术语

形成统一的领域术语,尤其是基于模型的语言概念,是沟通能够达成一致的前提。尤其是开发人员与领域专家之间,他们掌握的知识存在巨大的差异。善于技术的开发人员关注于数据库、通信机制、集成方式与架构体系,而精通业务的领域专家对这些却一窍不通,但他们在讲解业务知识时,使用各种概念如呼吸一般自然,这些对于开发人员来说,却成了天书,这种交流就好似使用两种不同语言的外国人在交谈。记得有一次我去洛杉矶出差,居住期间,需要到一家洗衣店干洗衣服,交付完衣服后,我想向洗衣店老板索要收据,以作为之后领取衣服的凭证。好不容易在我脑中贫瘠的英文词典里搜索到 receipt 这个词语,自以为正确,谁知道讲出来后老板一脸茫然,不是 receipt,那么是 ……invoice?手舞足蹈说了半天,老板才反应过来,递过来一张收据,嘴里吐出 ticket 这个词语,My God,受了中学英语的流毒,我还以为 ticket 这个词语只能用到电影院呢。

显然,从需求中提炼出统一语言,其实就是在两个不同的语言世界中进行正确翻译的过程。

某些领域术语是有行业规范的,例如财会领域就有标准的会计准则,对于账目、对账、成本、利润等概念都有标准的定义,在一定程度上避免了分歧。然而,标准并非绝对的,在某些行业甚至存在多种标准共存的现象。以民航业的运输统计指标为例,牵涉到与运量、运力以及周转量相关的术语,就存在 ICAO(International Civil Aviation Organization,国际民用航空组织)与IATA(International Air Transport Association,国际航空运输协会)两大体系,而中国民航局又有自己的中文解释,航空公司和各大机场亦有自己衍生的定义。

例如,针对一次航空运输的运量,就要分为城市对航段的运量统计。城市对运量统计的是出发城市到目的城市两点之间的旅客数量,机场将其称之为流向。ICAO 定义的领域术语为 City-pair(OFOD),而 IATA 则命名为 O & D航段运量又称为载客量,指某个特定航段上所承载的旅客总数量,ICAO将其定义为 TFS(Traffic by flight stage),而 IATA 则称为 Segment Traffic

即使针对航段运量这个术语,我们还需要明确地定义这个运量究竟指的是载客量,还是包含了该航段上承载的全部旅客、货物与邮件数量;我们还需要明确城市对航段之间的区别,它们在指标统计时,实则存在细微的差异,一不小心忽略,结果就可能谬以千里。以航班 CZ5724 为例,该航班从北京(目的港代码 PEK)出发,经停武汉(目的港代码 WUH)飞往广州(目的港代码 CAN)。假定从北京到武汉的旅客数为 105,从北京到广州的旅客数为 14,从武汉到广州的旅客数为 83,则统计该次航班的城市对运量,应该分为三个城市对分别统计,即统计 PEK-WUH、PEK-CAN、WUH-CAN。而航段运量的统计则仅仅分为两个航段 PEK-WUH 与 WUH-CAN,至于从北京到广州的 14 名旅客,这个数量值则被截分为了两段,分别计数,如下图所示:

显然,如果我们不明白城市对运量与航段运量的真正含义,就可能混淆这两种指标的统计计算规则。这种术语理解错误带来的缺陷往往难以发现,除非业务分析人员、开发人员与测试人员能就此知识达成一致的正确理解。

在领域建模过程中,我们往往需要在文档中建立一个大家一致认可的术语表。术语表中需要包括整个团队精炼出来的术语概念,以及对该术语的清晰明白的解释。若有可能,可以为难以理解的术语提供具体的案例。该术语表是领域建模的关键,是模型的重要参考规范,能够真实地反应模型的领域意义。一旦发生变更,也需要及时地对其进行更新。

在维护领域术语表时,一定需要给出对应的英文术语,否则可能直接影响到代码实现。在我们的一个产品开发中,根据需求识别出了“导入策略”的领域概念。由于这个术语非常容易理解,团队就此达成了一致,却没有明确给出英文名称,最后导致前端和后端在开发与“导入策略”有关的功能时,分别命名为 ImportingPolicy 与 ImportingStrategy,人为地制造了混乱。

即使术语的英语并不需要对外暴露给用户,我们仍然需要引起重视,就算不强调英文翻译的纯正,也必须保证概念的一致性,倘若认为英文表达不合理或者不标准,牵涉到对类、方法的重命名,则需要统一修改。在大数据分析领域中,针对“维度”与“指标”两个术语,我们在过去开发的产品中就曾不幸地衍生出了两套英文定义,分别为 Dimension 与 Metric,Category 与 Measure,这种混乱让整个团队的开发成员痛苦不堪,带来了沟通和交流的障碍。就我而言,我宁愿代码命名没有正确地表达领域概念,也不希望出现命名上的不一致性。倘若在建模之初就明确母语和英语的术语表达,就可以做到正本清源!

领域行为描述

从某种程度讲,领域行为描述可以视为领域术语甄别的一种延伸。领域行为是对业务过程的描述,相对于领域术语而言,它体现了更加完整的业务需求以及复杂的业务规则。在描述领域行为时,需要满足以下要求:

  • 从领域的角度而非实现角度描述领域行为
  • 若涉及到领域术语,必须遵循术语表的规范
  • 强调动词的精确性,符合业务动作在该领域的合理性
  • 要突出与领域行为有关的领域概念

例如,在项目管理系统中,倘若我们采用 Scrum 的敏捷项目管理流程,要描述 Sprint Backlog 的任务安排,则编写的用户故事如下所示:

作为一名Scrum Master,
我希望将Sprint Backlog分配给团队成员,
以便于明确Backlog的负责人并跟踪进度。

验收标准:
* 被分配的Sprint Backlog没有被关闭
* 分配成功后,系统会发送邮件给指定的团队成员
* 一个Sprint Backlog只能分配给一个团队成员
* 若已有负责人与新的负责人为同一个人,则取消本次分配
* 每次对Sprint Backlog的分配都需要保存以便于查询

用户故事中的分配(assign)Sprint Backlog 给团队成员就是一种领域行为,这种行为是在特定上下文中由角色触发的动作,并由此产生的业务流程和操作结果。同时,这种领域行为还是一种契约,明确地表达了服务提供者与消费者之间的业务关系,即明确了领域行为的前置条件、执行主语宾语以及行为的执行结果,这些描述丰富了该领域的统一语言,并直接影响了 API 的设计。例如,针对分配 Sprint Backlog 的行为,用户故事就明确了未关闭的 SprintBacklog 只能分配给一个团队成员,且不允许重复分配,这体现了分配行为的业务规则。验收标准中提出对分配的保存,实际上也帮助我们得到了一个领域概念 SprintBacklogAssignment,该行为的代码实现如下所示:

package practiceddd.projectmanager.scrumcontext.domain;


import practiceddd.projectmanager.dddcore.Entity;
import practiceddd.projectmanager.scrumcontext.domain.exception.InvalidAssignmentException;
import practiceddd.projectmanager.scrumcontext.domain.exception.InvalidBacklogException;
import practiceddd.projectmanager.scrumcontext.domain.role.MemberId;
import practiceddd.projectmanager.scrumcontext.domain.role.TeamMember;


public class SprintBacklog extends Entity<BacklogId> {
    private String title;
    private String description;
    private BacklogStatus backlogStatus;
    private MemberId ownerId;


    public SprintBacklog(BacklogId backlogId, String title, String description) {
        if (title == null) {
            throw new InvalidBacklogException("the title of backlog can't be null");
        }


        this.id = backlogId;
        this.title = title;
        this.description = description;
        this.backlogStatus = new NewBacklogStatus();
    }


    public SprintBacklogAssignment assignTo(TeamMember assignee) {
        if (this.backlogStatus.isClosed()) {
            throw new InvalidAssignmentException(
                    String.format("The closed sprint backlog %s can not be assigned to %s.", this.title, assignee.getName()));
        }
        if (assignee.isSame(this.ownerId)) {
            throw new InvalidAssignmentException(
                    String.format("The sprint backlog %s not allow to assign to same team member %s.", this.title, assignee.getName()));
       }
       return new SprintBacklogAssignment(this.id, assignee.id());
    }
}

基于“信息专家模式”,SprintBacklog 类的 assignTo() 方法只承担了它能够履行的职责。作为 SprintBacklog 对象自身,它知道自己的状态,知道自己是否被分配过,分配给谁,也知道遵循不同的业务规则会导致产生不同的结果。但由于它不具备发送邮件的知识,针对邮件发送它就无能为力了,因此这里实现的 assignTo() 方法仅仅完成了部分领域行为,若要完成整个用户故事描述的业务场景,需要交给领域服务 AssignSprintBacklogService 来完成:

package practiceddd.projectmanager.scrumcontext.domain;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import practiceddd.projectmanager.scrumcontext.domain.role.TeamMember;
import practiceddd.projectmanager.scrumcontext.interfaces.notification.NotificationService;


@Service
public class AssignSprintBacklogService {
    @Autowired
    private SprintBacklogRepository backlogRepository;
    @Autowired
    private SprintBacklogAssignmentRepository assignmentRepository;
    @Autowired
    private NotificationService notificationService;


    public void assign(SprintBacklog backlog, TeamMember assignee) {
        SprintBacklogAssignment assignment = backlog.assignTo(assignee);
        backlogRepository.update(backlog);
        assignmentRepository.add(assignment);


        AssignmentNotification notification = new AssignmentNotification(assignment);
        notificationService.send(notification.address(), notification.content());
    }  
}

注意:在这里将发送邮件的行为定义为领域行为,因此分配 Sprint Backlog 的业务行为被定义在了领域服务 AssignSprintBacklogService 中。如果将发送邮件视为是一种横切关注点,正确的做法则是将发送邮件的调用放到应用服务 SprintBacklogAppService 中。当然,一旦将该逻辑放到了应用服务,就存在如何组装邮件内容的问题,即前述方法中对 AssignmentNotification 实例的创建。针对这些疑问和解决方案在后续内容都有详细介绍。

定义和确定统一语言,将有利于消除领域专家与团队、以及团队成员之间沟通的分歧与误解,使得各种角色能够在相同的语境下行事,避免盲人摸象的“视觉”障碍。领域的统一语言还是领域建模的重要输入与基础,无论是采用“名词动词法”进行领域建模,还是“四色建模法”或“职责驱动建模”,统一语言都是确定模型的重要参考。如果在确定统一语言的同时,针对领域概念与领域行为皆以英文来表达,就直接为编码实现提供了类、方法、属性等命名的依据,保证代码自身就能直观表达领域含义,提高代码可读性。

磨刀不误砍柴工,多花一些时间去打磨统一语言,并非时间的浪费,相反还能改进领域模型乃至编码实现的质量,反过来,领域模型与实现的代码又能避免统一语言的“腐化”,保持语言的常新。重视统一语言,就能促成彼此正面影响的良性循环;否则领域模型与代码会因为沟通不明而泥足深陷,就真是得不偿失了。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ThreadLocal内存泄漏示例

ThreadLocal内存泄漏是老生常谈的问题了&#xff0c;原理就不多说了&#xff0c;这里只简单回顾下 Thread类有个属性threadLocals&#xff0c;其实就是个map。 这个map的结构如下&#xff0c;key是ThreadLocal对象&#xff0c;是一个弱引用&#xff0c;value是调用threadLocal…

机器学习周报第30周

目录 摘要Abstract一、文献阅读1 论文标题2 论文摘要3 过去方案4 论文方案5 相关代码 摘要 Abstract 一、文献阅读 1 论文标题 Accurate one step and multistep forecasting of very short-term PV power using LSTM-TCN model - ScienceDirect 2 论文摘要 准确的光伏功…

翻译: GPT-4 with Vision 升级 Streamlit 应用程序的 7 种方式二

GPT-4 Vision 系列: 翻译: GPT-4 with Vision 升级 Streamlit 应用程序的 7 种方式一 GPT-4 Vision 的 7 个实际用例 Pre-requisites:先决条件&#xff1a; 订阅 ChatGPT Plus 以访问 GPT-4 Vision。如果您不熟悉 Streamlit&#xff0c;请按照安装步骤操作。 1. 绘制您的应…

Java多线程编程中的异常处理策略

第1章&#xff1a;引言 大家好&#xff0c;我是小黑&#xff0c;咱们今天聊聊异常处理。想必大家在写代码的时候都遇到过各种各样的异常吧&#xff1f;有时候&#xff0c;一个小小的异常如果处理不当&#xff0c;就可能导致整个程序崩溃。特别是在多线程环境下&#xff0c;异常…

java关键字概述——final及常量概述

前言&#xff1a; 打好基础&#xff0c;daydayup! final final概述 final关键字是最终的意思&#xff0c;可以修饰&#xff08;类&#xff0c;方法&#xff0c;变量&#xff09; final作用 修饰类&#xff1a;该类被称为最终类&#xff0c;特点为不能被继承 修饰方法&#xff…

python 基础知识点(蓝桥杯python科目个人复习计划26)

今日复习内容&#xff1a;基础算法中的前缀和 1.定义&#xff1a; 前缀和&#xff1a;对于一个长度为n的列表a&#xff0c;前缀和为&#xff1a; sum[i] a[1] ...a[i];例如&#xff1a;a [1,2,3,4,5],则它的前缀和数组sum为&#xff1a;[1,3,6,10,15]。 2.前缀和的性质 …

【计算机网络】IP协议及动态路由算法

对应代码包传送门 IP协议及动态路由算法代码包及思科模拟器资料说明 相关文章 【计算机网络】中小型校园网构建与配置 【计算机网络】Socket通信编程与传输协议分析 【计算机网络】网络应用通信基本原理 目的&#xff1a; 1、掌握IP协议&#xff0c;IP分片&#xff0c;DH…

laravel框架项目对接小程序实战经验回顾

一.对接小程序总结 1.状态转换带来的问题&#xff0c;如下 问题原因&#xff1a;由于status 传参赋值层级较多&#xff0c;导致后续查询是数组但是传参是字符串&#xff0c; 解决方案&#xff1a;互斥的地方赋值为空数组&#xff0c;有状态冲突的地方unset掉不需要的参数 2参…

循环的乐章与爱情的旋律

循环的乐章与爱情的旋律 The Rhapsody of Loops and the Melody of Love 在一个阳光明媚的Java编程课上&#xff0c;男主角林浩然&#xff0c;一个热衷于代码逻辑和算法谜题的大二学生&#xff0c;正沉浸在他的Java世界里。而女主角杨凌芸&#xff0c;则是班级中出了名的“程序…

【AI量化分析】小明在量化中使用交叉验证原理深度分析解读

进行交叉验证好处 提高模型的泛化能力&#xff1a;通过将数据集分成多个部分并使用其中的一部分数据进行模型训练&#xff0c;然后使用另一部分数据对模型进行测试&#xff0c;可以确保模型在未见过的数据上表现良好。这样可以降低模型过拟合或欠拟合的风险&#xff0c;提高模…

多维时序 | Matlab实现DBO-LSTM蜣螂算法优化长短期记忆神经网络多变量时间序列预测

多维时序 | Matlab实现DBO-LSTM蜣螂算法优化长短期记忆神经网络多变量时间序列预测 目录 多维时序 | Matlab实现DBO-LSTM蜣螂算法优化长短期记忆神经网络多变量时间序列预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现DBO-LSTM多变量时间序列预测&#x…

【Android】实现简易购物车功能(附源码)

先上结果&#xff1a; 代码&#xff1a; 首先引入图片加载&#xff1a; implementation com.github.bumptech.glide:glide:4.15.1配置权限清单&#xff1a; <!-- 网络权限 --><uses-permission android:name"android.permission.INTERNET"/><uses…

【机器学习300问】16、逻辑回归模型实现分类的原理?

在上一篇文章中&#xff0c;我初步介绍了什么是逻辑回归模型&#xff0c;从它能解决什么问题开始介绍&#xff0c;并讲到了它长什么样子的。如果有需要的小伙伴可以回顾一下&#xff0c;链接我放在下面啦&#xff1a; 【机器学习300问】15、什么是…

【前端web入门第一天】02 HTML图片标签 超链接标签 音频标签 视频标签

文章目录: 1.HTML图片标签 1.1 图像标签-基本使用1.2 图像标签-属性1.3 路径 1.3.1 相对路径 1.3.2 绝对路径 2.超链接标签 3.音频标签 4.视频标签 1.HTML图片标签 1.1 图像标签-基本使用 作用:在网页中插入图片。 <img src"图片的URL">src用于指定图像…

Kong: Services and Routes 等基本属性

Services 在Kong Gateway中&#xff0c;服务是现有上游应用程序的抽象。服务可以存储插件配置和策略等对象的集合&#xff0c;并且可以与路由相关联。 定义服务时&#xff0c;管理员会提供名称和上游应用程序连接信息。连接详细信息可以在 url 字段中以单个字符串的形式提供…

Kotlin 教程(环境搭建)

Kotlin IntelliJ IDEA环境搭建 IntelliJ IDEA 免费的社区版下载地址&#xff1a;Download IntelliJ IDEA – The Leading Java and Kotlin IDE 下载安装后&#xff0c;我们就可以使用该工具来创建项目&#xff0c;创建过程需要选择 SDK&#xff0c; Kotlin 与 JDK 1.6 一起使…

【c语言】人生重开模拟器

前言&#xff1a; 人生重开模拟器是前段时间非常火的一个小游戏&#xff0c;接下来我们将一起学习使用c语言写一个简易版的人生重开模拟器。 1.实现一个简化版的人生重开模拟器 &#xff08;1&#xff09; 游戏开始的时候&#xff0c;设定初始属性&#xff1a;颜值&#xf…

新建一个基于标准库的工程(STM32)

目录 1.新建存放工程的文件夹 2.打开KEIL5软件 3.新建一个本次工程的文件夹 4.添加工程的必要文件 4.1打开STM32的启动文件 ​编辑 4.2&#xff1a; 4.3添加内核寄存器文件 ​编辑 5.回到keil5软件&#xff0c;将刚才复制的那些文件添加到工程中 5.1添加一个启动文件&am…

【服务器数据恢复】EqualLogic存储磁盘坏道导致存储不可用的数据恢复案例

服务器数据恢复环境&故障&#xff1a; 某公司IT部门一台某品牌EqualLogic PS6100系列存储在运行过程中突然崩溃。 服务器管理员对故障服务器存储进行初步检查&#xff0c;经过检测发现导致该服务器存储无法正常工作的原因是该存储中raid5磁盘阵列内有2块硬盘出现故障离线&a…

VitePress-03-标题锚点的使用与文档内部链接跳转

说明 本文介绍如下内容&#xff1a; 1、vitepress 中 md 文件中的标题锚点 2、锚点的使用 &#xff1a; 文档内部的快速跳转 锚点 什么是锚点 锚点 &#xff1a; 通俗的理解就是一个位置标记&#xff0c;通过这个标记可以快速的进行定位。 【vitepress 中&#xff0c;md 文档的…