【DDD】学习笔记-统一语言与领域分析模型

2024-02-11 10:28

本文主要是介绍【DDD】学习笔记-统一语言与领域分析模型,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

无论你采用什么样的软件开发过程,对于一个复杂的软件系统,都必然需要通过分析阶段对问题域展开分析,如此才能有的放矢地针对该软件系统的需求寻找设计上的解决方案。在领域驱动设计中,分析阶段完全围绕着“领域”为中心展开,最终获得的领域模型即领域分析模型。开发团队应该与领域专家一起分析系统的用户需求,然后建立初步的领域分析模型。在进行分析建模时,一个重要参考是整个系统的统一语言(Ubiquitous Language)。

统一语言与领域分析模型

回顾领域驱动的战略设计阶段,我们引入了敏捷开发的先启(Inception)实践促进团队与领域专家以及客户的充分交流。先启阶段的如下活动与提炼领域知识直接相关:

img

通过先启阶段,团队对整个系统的范围、目标与愿景达成了一致,并通过展开识别核心领域(Core Domain)子领域(Sub Domain)对问题域进行了合理的分解。问题域的识别与分解在一定程度上降低了系统的业务复杂度。针对核心领域,结合识别出来的史诗级故事与主故事,我们利用领域场景分析来提炼领域知识,获得整个系统的统一语言

在领域驱动设计中,怎么强调统一语言都不为过!无论是在战略阶段还是战术阶段,我们都能看到统一语言的身影。它是战略设计阶段的重要模式,可以帮助我们梳理业务知识,以此来识别问题域。在识别限界上下文时,是统一语言提出了概念边界,才给了我们判断限界上下文边界的标准。领域模型所要表达的业务概念更是要遵守统一语言,保证分析模型、设计模型与实现模型的一致性。统一语言是领域模型的核心参考!

因此,当我们想要获得领域分析模型时,首先需要参考的就是统一语言,它可以有效地帮助我们识别出整个模型中最核心也是最基本的显式领域概念。

那么统一语言到底出现在哪里?它似乎无处不在,然而正因为此,又似乎缺乏足够明确的规范。就我个人理解,所谓“统一语言”,并非某一种固定格式的交付物,而是领域驱动设计过程中无形的最高设计准绳。为了保证分析与设计的质量,我们需要不停地追问:

  • 我们设计的模型符合统一语言吗?
  • 限界上下文的领域概念遵循统一语言吗?
  • 类名与方法名满足统一语言的规范吗?

这就好比你开车到一个陌生的城市。统一语言就是地图导航,不停地发出声音提醒你行进的方向,当你驶入错误的地方时,它也会及时地修正路线,然后给予你正确的提示。

我在为一家物流公司提供领域驱动咨询时,发现他们对运输的定义未曾形成统一语言。他们认为运输是一个单段运输,整体的一个多式联运则被认为是一项委托。表面看来,委托是客户提出的需求订单,然而经过我和他们一起分析领域概念,发现承运人在确认委托时,需要对整个运输过程制定计划。这个运输可能是从 A 到 B 的铁路运输,也可能是 B 到 C 的公路运输。从 A 到 C 的运输被视为一个多式联运的委托,其中 B 为铁路堆场,C 为货站。由于没有确定统一语言,团队对运输和委托的领域概念混淆不清。

经过分析,我们一致认为应该将运输(Shipment)理解为从起点到终点的整个运输过程,整个运输过程可能会经过多个站点(Station),站点包括堆场和货站,两个站点之间的运输则被称为运输段(Segment)。运输上下文包括运输计划与路径线的管理。堆场和货站是两个完全不同的概念,堆场针对的资源是集装箱,货站针对的资源是件散货。用于装卸货的工作区域和用于存储货物的仓库组成一个独立的货站限界上下文。堆场限界上下文则包含堆场区域信息管理与掏箱、转场和修箱。在运输上下文,堆场和货站被抽象为站点,并不牵涉到站点内部的管理。这就使得运输与站点之间的逻辑互不干扰。

img

在建立了运输上下文的领域模型之后,我们发现铁路运输和公路运输可以合并到同一个运输领域模型中,体现为运输的两种方式。开发团队在日常交流和讨论中提及的委托、规划与计划,其实是同一个概念,定义其统一语言为运输规划

如果希望将统一语言固化到某一个实践中,使之成为我们领域建模的参考,那就是领域场景分析的产物。我在《领域驱动战略设计实践》课程中给出了三种不同的领域场景分析方法:用例、用户故事和测试驱动开发,它们恰好对应了分析、设计与实现三个阶段。因此,对于领域分析模型而言,我们可以参考遵循统一语言的用例。这也正是我为何反复强调用例表达的领域概念必须精准的主要原因。

在战略部分的领域场景分析中,我写道:

在为每个用例进行命名时,我们都应该采纳统一语言中的概念,然后以言简意赅的动宾短语描述用例,并提供英文表达。很多时候,在团队内部已经形成了中文概念的固有印象,一旦翻译成英文,就可能呈现百花齐放的面貌,这就破坏了“统一语言”。为保证用例描述的精准性,可以考虑引入“局外人”对用例提问。局外人不了解业务,任何领域概念对他而言可能都是陌生的。通过不断对用例表达的概念进行提问,团队成员就会在不断的阐释中形成更加清晰的术语定义,对领域行为的认识也会更加精确。

在针对一款供应链产品进行领域分析建模时,资金团队识别出来的部分用例如下所示:

img

:这里绘制的用例图并未采用 UML 的标准用例图形式,各种颜色的便利贴分别代表了参与者、主用例和子用例。我希望通过这种形式再次说明利用可视化工具进行领域建模的重要性。

用例描述要求言简意赅,但并不意味着我们不追求用例描述的精确。图中的付现汇和付票据主用例都包含了“提交银行”子用例。这个用例的描述语焉不详,甚至会被认为是同一个重用的子用例。经过不断交流,才发现这里遗漏了重要的领域概念。付现汇主用例中的“提交银行”子用例其实是“提交收款指令”,而付票据主用例的“提交银行”子用例则是“提交电票指令”。因此,类似用例这样的领域场景分析方法,是领域分析模型的重要源头,若源头被“污染”了,就会影响到领域分析模型的质量。对领域场景的分析,必须字斟句酌,比作家对待写作还要精确与严谨。

无论是用例,还是用例图,只要遵照了这样的分析要求,我们就可以利用“名词动词法”来初步梳理该问题域的领域概念,并获得这些领域概念之间的关系。这种方法由 Russell Abbott 提出,在他 1983 年发表的论文 Program Design by Informal English Description 中,他建议写下问题的英语描述,然后划出名词和动词。名词代表了候选对象,动词代表了这些对象上的候选操作。倘若采用用例分析,则一个用例就是一句英语描述。例如电商系统下订单的用例图如下所示:

59800642.png

用例描述中的名词对应于领域分析模型中的类型或类型的属性。注意,即使是属性,如果该属性表达了一个领域概念,同样应该定义为类型,如“calculate shipping fee”用例中的名词为 Shipping Fee,它是订单(Order)的属性,但它同样体现了“运费”这一个重要的领域概念。用例中的参与者在领域分析模型中同样应该被定义为类型,它与构成主用例宾语的领域概念之间存在关联关系。

比起用例,用例图要更加精炼。若在领域场景分析过程中,以获得用例图为目标,就可以降低领域分析建模阶段的成本。然而,凡事有利就有弊,这种精炼的形式也可能会漏掉一些必要的业务概念。这些业务概念往往是领域模型中主要领域概念的附属概念,如订单(Order)与订单项(OrderItem)、购物车(ShoppingCart)与购物车选项(CartItem)。

在分析用例时,也要注意用例描述可能带来的分析陷阱或隐藏的概念。例如“validate inventory”用例中,宾语为库存(Inventory),但在下订单领域场景中,检查的其实是商品的库存量。当然,这也可能反过来说明在绘制用例图时,我们对用例的描述欠妥当。“notify buyer”用例较为特殊,表面上它表达的领域行为就是“通知买家”,但实际上这里隐藏了一个“通知(Notification)”的名词概念。

综上分析,利用名词动词法自然就能获得如下的分析模型:

img

在这个模型中,我定义了 OrderPlacedNotification 而非 Notification,这是希望清晰地表达下订单场景的通知行为。粗略一看,OrderPlacedNotification 需要包含买家的联系信息和订单内容,似乎足以证明这三者之间存在关联关系。仔细分析,却发现在创建 OrderPlacedNotification 时,确实需要买家和订单的信息,然而一旦创建,它就成了自给自足的通知对象,与模型中的 Buyer 和 Order 再也无关。在领域模型中,没有关系也是一种关系,只要表达了真实的领域逻辑,就是合理的。

在建立领域分析模型时,必须慎重确定类型之间的关系。由于分析活动并未深入太多业务细节,在分析建模过程中,应只考虑显而易见且明确无误的关联关系,如 Order 与 OrderItem、ShoppingCart 与 CartItem,以及在用例图中表达出来的 Buyer 与 Order 之间的关系。除了没有关系这种特殊关系外,模型概念之间的关系不外乎一对一、一对多和多对多。在建立领域分析模型时,最好也能确定具体的关系类型。但是,由于“名词动词法”这种分析建模方法稍显简陋,只适用于领域分析建模的早期。在这个阶段,识别主要的领域概念才是建模的重心,至于关系类型的确定,可以留待领域分析模型的精炼阶段。

这篇关于【DDD】学习笔记-统一语言与领域分析模型的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/699554

相关文章

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

Java深度学习库DJL实现Python的NumPy方式

《Java深度学习库DJL实现Python的NumPy方式》本文介绍了DJL库的背景和基本功能,包括NDArray的创建、数学运算、数据获取和设置等,同时,还展示了如何使用NDArray进行数据预处理... 目录1 NDArray 的背景介绍1.1 架构2 JavaDJL使用2.1 安装DJL2.2 基本操

Go语言中三种容器类型的数据结构详解

《Go语言中三种容器类型的数据结构详解》在Go语言中,有三种主要的容器类型用于存储和操作集合数据:本文主要介绍三者的使用与区别,感兴趣的小伙伴可以跟随小编一起学习一下... 目录基本概念1. 数组(Array)2. 切片(Slice)3. 映射(Map)对比总结注意事项基本概念在 Go 语言中,有三种主要

C语言中自动与强制转换全解析

《C语言中自动与强制转换全解析》在编写C程序时,类型转换是确保数据正确性和一致性的关键环节,无论是隐式转换还是显式转换,都各有特点和应用场景,本文将详细探讨C语言中的类型转换机制,帮助您更好地理解并在... 目录类型转换的重要性自动类型转换(隐式转换)强制类型转换(显式转换)常见错误与注意事项总结与建议类型

Go语言利用泛型封装常见的Map操作

《Go语言利用泛型封装常见的Map操作》Go语言在1.18版本中引入了泛型,这是Go语言发展的一个重要里程碑,它极大地增强了语言的表达能力和灵活性,本文将通过泛型实现封装常见的Map操作,感... 目录什么是泛型泛型解决了什么问题Go泛型基于泛型的常见Map操作代码合集总结什么是泛型泛型是一种编程范式,允

Android kotlin语言实现删除文件的解决方案

《Androidkotlin语言实现删除文件的解决方案》:本文主要介绍Androidkotlin语言实现删除文件的解决方案,在项目开发过程中,尤其是需要跨平台协作的项目,那么删除用户指定的文件的... 目录一、前言二、适用环境三、模板内容1.权限申请2.Activity中的模板一、前言在项目开发过程中,尤

Spring Boot统一异常拦截实践指南(最新推荐)

《SpringBoot统一异常拦截实践指南(最新推荐)》本文介绍了SpringBoot中统一异常处理的重要性及实现方案,包括使用`@ControllerAdvice`和`@ExceptionHand... 目录Spring Boot统一异常拦截实践指南一、为什么需要统一异常处理二、核心实现方案1. 基础组件

C语言小项目实战之通讯录功能

《C语言小项目实战之通讯录功能》:本文主要介绍如何设计和实现一个简单的通讯录管理系统,包括联系人信息的存储、增加、删除、查找、修改和排序等功能,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录功能介绍:添加联系人模块显示联系人模块删除联系人模块查找联系人模块修改联系人模块排序联系人模块源代码如下

基于Go语言实现一个压测工具

《基于Go语言实现一个压测工具》这篇文章主要为大家详细介绍了基于Go语言实现一个简单的压测工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录整体架构通用数据处理模块Http请求响应数据处理Curl参数解析处理客户端模块Http客户端处理Grpc客户端处理Websocket客户端

使用SQL语言查询多个Excel表格的操作方法

《使用SQL语言查询多个Excel表格的操作方法》本文介绍了如何使用SQL语言查询多个Excel表格,通过将所有Excel表格放入一个.xlsx文件中,并使用pandas和pandasql库进行读取和... 目录如何用SQL语言查询多个Excel表格如何使用sql查询excel内容1. 简介2. 实现思路3