软创互联

DDD 领域驱动设计学习(一)- 领域模型和统一语言

领域驱动设计
ddd

#1

1. DDD是什么?解决什么问题?

1.1 软件开发的困境

  • “随着业务的扩展,软件开发投资越来越大” 团队的规模也开始变得越来越大,软件系统的投资和维护的成本变得越来越高。
  • “业务人员不懂架构,架构师不懂代码,开发人员不不懂业务模型” 当团队中的关键角色谁也不懂谁的时候,问题来了。。。
  • “重构是好的,但什么时候要重构?重构到什么样的架构就是够⽤的了?” 每个有追求的团队都在做重构,但管理者更关心,什么时间必须要重构?重构的目标在哪?

1.2 DDD的来源及简介

2004年Eric Evans 发表Domain-Driven Design –Tackling Complexity in the Heart of Software (领域驱动设计),简称DDD,DDD是一套综合软件系统分析和设计的面向对象建模方法。
Eric的著名书籍《领域驱动设计》的副标题是“软件核心复杂性的应对之道”,这个其实点出了DDD的来源和目标,很多因素会使软件的开发复杂化。软件是从现实世界到数字世界的一种建模和映射,软件复杂性的根本原因还是业务本身复杂性,软件开发者无法回避这种复杂性,所能做的是去控制这种复杂性。
怎样才能让软件和领域和谐相处呢?最佳方式是让软件成为领域的一个映射。软件需要包含领域里重要的核心概念和元素,并精确实现它们之间的关系。也就是说,软件需要对领域进行建模。
领域驱动设计分为两个阶段:以一种领域专家、设计人员、开发人员都能理解的通用语言作为相互交流的工具,在交流的过程中发现领域概念,然后将这些概念设计成一个领域模型;第二个阶段是由领域模型驱动软件设计,用代码来实现该领域模型。
从DDD提出到开始流行,感觉经过了10年左右的时间,巧的是XP和敏捷从提出到流行也差不多10年左右的时间。可见一套方法论从出现到成熟确实是有一定规律并需要成长时间的。
按Martin Fowler在PoEAA一书中给了一个图。说明当软件在开发初期,以数据驱动的架构方式非常容易上手,但是随着业务的增长和项目的推进,软件开发和维护难度急剧升高。领域驱动设计则在项目初期就处在一个比较难以上手的位置,但是随着业务的增长和项目的推进,软件开发和维护难度平滑上升。

Adapted from Martin Fowler’s PoEAA

2. 模型和建模

2.1 什么是模型

  1. 模型是对客观世界事物的一种抽象和简化。
  2. 它是从某个角度反映人对客观世界事物的一种认识。
  3. 它用于对事物的本质进行深入细致的研究。

2.2 模型的例子

2.2.1 地图的例子

地图就是一种典型的模型。现实世界的地理信息往往很复杂,一个地理位置上会包括非常多的信息,例如有位置信息,地形,气候等信息,也有城市,道路,绿化,管网,建筑等这些城市建设信息,还可能会有拥堵情况,商场,文化等各种附加于地理信息的各种信息在里面。如何表达一个地图信息其实并不容易,如何表达一个地图信息是一件非常复杂的事情。
我们常见的地图,一般都会缩小尺寸,忽略很多细节,分不同类型的地图,现在还有图层的方式来表达不同地理信息。地图其实就是一种模型,而且地图很多建模方式在软件设计中也能找到映射。
看两个地图例子,建模从古至今就存在,而且随着人们认知水平和建模能力还会不断的演进。

图一、 古代天下地图

图二、 百度地图例子

回到软件研发的话题,软件的研发也是一种建模的过程,传统的方法中,从问题空间到解决方案的空间,会经过需求采集和分析,概要设计,详细设计再到编码的过程。正如我们国家“系统分析师” 和“系统设计师” 两种职称考试一样,系统分析和系统设计是分离的,“系统分析师” -BA属于业务专家,“系统设计师”-SA是系统专家。随着信息不断传递的过程,信息也在不断的失真,导致可能到了最后交付阶段才发现很多功能不是客户想要的,或者客户需求变化太快,软件的变更也不能快速跟随需求变化。
再举一个常见的例子:一个函数写了几千行,里面的if-else写了一大堆,计算各种业务规则。另一个人接手之后,分析了好几个月,才把业务逻辑彻底理清楚。从表面来看,这是代码写的不规范,要重构,把一个几千行的函数拆成一个个小的函数。从根本上来讲,就是“重要逻辑”隐藏在代码里面,没有“显性”的表达出来。这里可以引用一个观点:建模的本质就是把“重要的东西进行显性化,并进而把这些显性化的构造块,互相串联起来,组成一个体系“。
DDD通过建立一个业务域到软件域的通用模型,把问题空间同解决方案空间联系在一起,真正把领域的知识挖掘出来,让领域专家可以去驱动软件的实现。

3. 统一语言(UBIQUITOUS LANGUAGE)

解决问题首先要从理解问题入手,很多事情的难点不在于解决问题,而在于认知问题。关于统一语言必要性,有一个经典的通天塔故事,人类想建一座通天塔,进度很快,上帝害怕了,于是上帝让建造者说不通的语言,这样通天塔就再也没有能建起来了。统一语言是一件事情能顺利开展的基础。
由于语言上存在鸿沟,领域专家们只能模糊地描述他们想要的东西,开发人员虽然努力去理解一个自己不熟悉的领域但也只能形成模糊的认识,结果就是各说各的话,或者都是一知半解,最后到上线前才会发现漏了这个漏了那个。
通用语言也并不是像UML,XML Schema或Java这样的语言,它是一种自然的但经过浓缩的领域语言,它是一种开发与用户共享的语言,用来描述问题和领域模型。通用语言不是把从用户那里听到的内容翻译为开发的语言,而是为了减少误解,让用户更容易理解的草图,从而可以真正的帮助纠正错误,帮助开发获取有关的领域新知识。
那么统一语言到底长什么样子?对DDD的UL没有标准的定义,可以是图,也可以是文字,UML的各种图依然是常见的表达方式,以DDD书中例子来说明下UL的一个应用。
示例:制定货运路线,比较两个图和两段沟通交流的方式

  • 场景1:最小化的领域抽象

  • 场景2:用领域模型进行讨论

  • 两种沟通方式的比较
最小化的领域抽象 用领域模型进行讨论
用户 那么,当更改清关(customs clearance)地点时①,需要重新制定整个路线计划啰。 那么,当更改清关地点时,需要重新制定整个路线计划啰。
开发人员 是的。我们将从货运表(shipment table)中删除所有与该货物id相关联的行,然后将出发地、目的地和新的清关地点传递给Routing Service,它会重新填充货运表。Cargo中必须设立一个布尔值,用于指示货运表中是否有数据。 是的。当更改Route Specification(路线说明)的任意属性时,都将删除原有的Itinerary(航线),并要求Routing Service(路线服务)基于新的Route Specification生成一个新的Itinerary。
用户 删除行?好,就按你说的做。但是,如果先前根本没有指定清关地点,也需要这么做吗? 如果先前根本没有指定清关地点,也需要这么做吗?
开发人员 是的,无论何时更改了出发地、目的地或清关地点(或是第一次输入),都将检查是否已经有货运数据,如果有,则删除它们,然后由Routing Service重新生成数据。 是的,无论何时更改了Route Spec的任何属性,都将重新生成Itinerary。这也包括第一次输入某些属性。
用户 当然,如果原有的清关数据碰巧是正确的,我们就不需要这样做了。 当然,如果原有的清关数据碰巧是正确的,我们就不需要这样做了。
开发人员 哦,没问题。但让Routing Service每次重新加载或卸载数据会更容易些。 哦,没问题。但让Routing Service每次重新生成一个Itinerary会更容易些。
用户 是的,但为新航线制定所有支持计划的工作量很大,因此,除非非改不可,我们一般不想更改航线。 是的,但为新航线制定所有支持计划的工作量很大,因此,除非非改不可,我们一般不想更改路线。
开发人员 哦,好的,当第一次输入清关地点时,我们需要查询表格,找到以前的清关地点,然后与新的清关地点进行比较,从而判断是否需要重做。 哦。那么需要在Route Specification添加一些功能。这样,当更改Route Specification中的属性时,查看Itinerary是否仍满足Specification。如果不满足,则需要由Routing Service重新生成Itinerary。
用户 这个处理不必考虑出发地和目的地,因为航线在此总要变更。 这一点不必考虑出发地和目的地,因为Itinerary在此总是要变更的。
开发人员 好的,我明白了。 好的,但每次只做比较就简单多了。只有当不满足Route Specification时,才重新生成Itinerary。

很明显,这两段对话有意使用了相似的结构,第一段对话显得更啰嗦,对话双方需要不断对应用程序的特性和表达不清的地方进行解释。第二段对话使用了基于领域模型的术语,因此讨论更简洁,表达了领域专家的更多意图。在这两段对话中,用户都使用了“itinerary”这个词,但在第二段中它是一个对象,这使得双方可以更准确、具体地进行讨论。他们明确讨论了“route specification”,而不是每次都通过属性和过程来描述它。

作者:njluz
链接:https://www.jianshu.com/p/39c557f5b87f
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。