DDD开篇

从知道DDD到现在已经很多年了,看了不少理论知识,在项目中也使用了DDD,碰到些问题,也有些思考,整理一下,上升一下,形成一种适合自身的方法论

在回顾过程中,首先追根溯源,什么是DDD?为什么要使用DDD?如何给别人阐述这些最基本的概念与理念,真是个难题

什么是DDD

DDD已经发展了很多年,现在的一些书也已经不太关注这个基本概念,
平时闲聊时,开口闭口都是DDD,已经不知道DDD的本体是什么,只是听得耳熟,说得顺口了,细细回想下,DDD是个什么?一时语顿,不得找一下Eric Evans大神最原味的解答

DDD来源于Eric Evans的书:
《Domain-Driven Design - Tackling Complexity in the Heart of Software》
领域驱动设计 - 软件核心复杂性应对之道

由此可看出DDD的全称Domain-Driven Design,之前常听到TDD,那何为Domain呢?

书中解释:

每人软件程序都会与其用户的活动或兴趣相关。用户在其中使用程序的主要环境称为软件的领域(domain)

一些领域会涉及到物质世界:航线预订程序的领域涉及到现实中登机的人。一些领域是无形的:财务程序的领域就是货币和金融

领域模型并不是某种特殊的图,而是图所要表达的思想。它并不仅仅是某个领域专家头脑中的知识,而是对相关知识进行严格的组织与选择性抽象

领域模型并不是尽可能地制作一个逼真的模型。即使在一个可触及的真实世界事物的领域中,我们的模型也只是一个仿真的创造物

看到这些,应该还是有点晕,到底是什么?解释这种基本元语,特别的难,有人感觉领域就是业务,那为什么不叫Business Driven Design呢?

有点钻牛角尖的意思,这其实是人类学习新知识的一种惯性思维,从以往经验中寻求类似的事物,帮助理解新事物,好处就可以快速认知新知识,坏处可能永远掌握不了新知识。既然给不了一种清晰的类比解释,那就不求甚解地理解,这个新事务的代名词叫DDD,随着认识的深入,慢慢就会从抽象到具体

但为了提前更好地理解DDD,还是有必要把一些现流行的解读罗列出来

银行业务被银行的内部人员和专家所熟知。他们知道所有的细节、所有的困难、所有可能 出现的问题、所有的业务规则。这些就是我们永远的起始点:领域

领域驱动设计当然不是架构方法,也并非设计模式。准确地说,它其实是“一种思维方式,也是一组优先任务,它旨在加速那些必须处理复杂领域的软件项目的开发”。领域驱动设计贯穿了整个软件开发的生命周期,包括对需求的分析、建模、架构、设计,甚至最终的编码实现,乃至对编码的测试与重构

领域模型是关于某个特定业务领域的软件模型。通常,领域模型通过对象模型来实现,这些对象同时包含了数据和行为,并且表达了准确的业务含义

DDD专门解决复杂性的方法论

DDD是面向对象分析的方法论,它可以利用面向对象的特性(封装,多态)有效地化解复杂性,而传统的J2EE等事务性编程只关心数据

DDD作为一种软件开发方法,它可以帮助我们设计高质量的软件模型

由前人的种种总结,粗略可以把DDD理解为一种方法论,一种构造软件模型的方法论

模型

软件的开发目的是什么?软件开发是为了解决由需求带来的各种问题,而解决的结果是一个可以运行的交付物。那么软件设计就是在需求和解决方案之间架设一座桥梁

区别于解决简单的问题,软件的开发往往是一项长期的工作,会有许多人参与其中。在这种情况下,就需要建立起一个统一的结构,以便于所有人都能有一个共同的理解。这就如同建筑中的图纸,懂建筑的人看了之后,就会产生一个统一的认识。而在软件的开发过程中,这种统一的结构就是模型,而软件设计就是要构建出一套模型

模型是对现实世界的简化抽象

根据使用场景的不同,模型大致可以分为物理模型、概念模型、数学模型和思维模型等等

物理模型是拥有体积及重量的物理形态概念实体物体,是根据相似性理论制造的按原系统比例缩小的实物

概念模型是对真实世界中问题域内的事物的描述,是领域实体,而不是对软件设计的描述,它与技术无关。

数学模型是用数学语言描述的一类模型,可以是一个或一组代数方程,微分方程、差分方程、积分方程或统计学方程,也可以是某种适当的组合数学模型

思维模型是用简单易懂的图形、符号或者结构化语言等表达人们思考和解决问题的形式

建立模型有很多方法,并不意味着要用特定符号、工具和流程。无论使用何种建模工具和表示法,只要有助于我们对问题域的理解,均可认为是好的模型。在处理问题时,我们最好隐藏那些不必要的细节,只专注于重要的方面,抓住问题的本质。这就是建模和抽象的价值所在

在软件领域,影响最强的建模工具当属统一建模语言(Unified Modeling Language,UML)

模型,是一个软件的骨架,是一个软件之所以是这个软件的核心。一个电商平台,它不用关系型数据库,还可以用 NoSQL,但如果没有产品信息,没有订单,它就不再是电商平台了

模型的粒度可大可小,一个类是模型,把一整个系统当作一个整体理解,就是个大模型。而常听说的“高内聚、低耦合”其实就是对模型的要求。一个高内聚低耦合的模型能够有效地隐藏细节,让人理解起来也更容易,甚至还可以在上面扩展

在DDD中,模型的选择取决于三个基本用途:

  1. 模型与设计核心的相互塑型。正是模型与实现之间密切的联系使得模型与现实相关并且保证对于模型的讨论分析能够应用于最终产品–可运行的程序
  2. 模型是所有团队成员所使用语言的核心。由于模型与实现是相互绑定的,因此开发人员可以用这种语言来讨论程序
  3. 模型用来提炼知识。模型是团队在组织领域知识和辨别最感兴趣的原理时一致同意的方式

方法论

由上文可知DDD是一种开发方法,那在DDD之前,是怎么进行软件分析设计的呢?一般有两种方法:ER数据建模法和面向对象建模法

在软件的世界里,任何的方法论如果最终不能落在“减少代码复杂度”这个焦点上,那么都是有待商榷的。

ER

这是大多数人进行软件行业时,必学的方法,并且在学生时代,实践课程都是以此为示例,导致这种方法在脑海中根深蒂固

ER数据建模法是在接受到需求以后直接开始数据表ER模型的设计、实体表和表关系的设计

建模过程是一种翻译再表达的过程,其唯一要点就是翻译不走样,如果翻译过程过多引入其他干扰因素和知识,那么无疑会增加翻译的难度和复制的精确性

由于人们对数据库特别看重,SQL普及化,以至将软件的理解浓缩成了CRUD

但由于过于注重数据库技术而忽视了业务上下文。使用CRUD代替业务用语,例如使用“创建订单”替代“下单”,使用“创建帖子”代替“发贴”,使用“创建发票”替代“开票”,虽然也容易让人明白,但“开票”等用语才是真正的业务术语、业务行话,是这个行业内每个人都知晓的。作为软件系统不是去遵循这些用语习惯,而是进行转换改造,按照自己的理解生造出一些词,是不是会潜移默化地将业务需求引导到不同方向呢?

这种方法门槛低,带来了效率的提升,但是高效率不代表高质量,而软件高质量却能带来高效率

OOAD

业务初期,功能比较简单,CRUD基本可以满足。但随着系统的不断演化,业务系统越来越复杂,各模块间有着千丝万缕的关系,如何提升其扩展性

OO最大的宣言就是Everything is object。所有的事物都有两个方面:有什么(属性):用来描述对象;能够做什么(方法):告诉外界对象有那些功能。

把人们从低层的SQL计算机语言更加面向人类思维

Object-Oriented Analysis (OOA):面向对象的分析与设计的侧重点是业务领域分析,与软件所要应用的行业领域相关,而与软件技术关系不大,需要由领域专家进行。这一部分的工作被称为“需求分析”

OOA的成果:

业务领域用例图、活动图、协作图、大量的业务文档资料

Object-oriented design (OOD),用面向对象的方法为真实世界建立一个计算机中的虚拟模型,OOD的主要任务是跨越业务领域模型与可实际运行的软件系统之间的鸿沟,OOD的难度是非常大的,负责OOD工作的人被称为系统架构设计师

系统架构设计师的任务:

  1. 确定系统的总体框架—大多采用已有的领域框架
  2. 正确理解需求分析得出的领域模型,用面向对象的思想设计出软件体系结构—系统概要设计
  3. 分析现实的可获取的技术资源,分解出软件的各个组件,安排好开发任务流程—系统详细设计

从上面的表述,可以看出面向对象相比ER模型强大多了,但其实面向对象的思想至今都没有真正意义得到普及,至少我是这么认为的。为什么呢?看多少人在进行CRUD这种面向过程式编程就知道了,而且早年的J2EE开发模式,讲究 Web/Service/Dao三层结构。面向过程编程,对象只是数据的载体,没有行为。以数据为中心,以数据库ER设计作驱动。分层架构在这种开发模式下,可以理解为是对数据移动、处理和实现的过程。该对象我们称之为贫血领域对象

贫血领域对象(Anemic Domain Object):是指仅用作数据载体,而没有行为和动作的领域对象。大量的业务逻辑写在了Service层中,随着业务逻辑复杂,业务逻辑、状态会散落在Service层中的很多处理类或方法中。将数据和行为割裂,原来的代码意图会越来越模糊,代码的理解和维护成本会越来越高。

虽然面向对象没有得到真正的普及,但面向对象还是带来了很多思想,我不确定这些思想是不是面向对象之后才有的,但至少很多思想的确是在学习面向对象后才知道的

比如之前写过一个系列《SOLID》,就是面向对象设计原则

而且像Robert C.Martin认为一个可维护性较低的软件设计,通常由于如下4个原因造成:

  1. 过于僵硬Rigidity
  2. 过于脆弱Fragility
  3. 复用率低Immobility
  4. 黏度过高Viscosity

软件工程和建模大师Peter Coad认为,一个好的系统设计应该具备如下三个性质:

  1. 可扩展性
  2. 灵活性
  3. 可插入性

而想规避缺点拥有优点,面向对象设计提供很大的便利,软件的复用(Reuse)或重用拥有众多优点,如可以提高软件的开发效率,提高软件质量,节约开发成本,恰当的复用还可以改善系统的可维护性,这些都是以面向对象设计原则为基础而来

对象与数据表的关系?

在从ER建模转向OOAD时,常把对象类比成表,以前称为表,现在换个名字叫对象,他们好似是一样的,但其实他们的差别很大,就文章开关所述,这是人的经验主义,可能永远掌握不了新知识

数据表是一种数据结构,数据表中的数据是需要SQL去操作的,也就是说,数据结构中的数据是被外部某些行为或函数操作的;虽然对象或类中封装的属性其实也是数据,但对象或类有行为方法,这些行为可以保护被封装的属性数据,外界需要改变对象中的属性数据时,必须通过公开的行为方法才能实现。因此,对象和数据结构两者的区别之一就于在对数据操作是主动还是被动,对象是主动操作数据,而数据结构是被动操作,这一区别使得两种方式下的分析设计思路和编程范式完全不同

表达业务领域中的业务概念时,强调主动操作数据的类或对象更适合表达业务概念,因为业务领域中的业务策略或业务规则都需要动态操作,它们的逻辑性需要主动操作数据完成

设计时,业务对象是定义业务行为,而数据表是定义业务数据结构。一个注重行为,一个注重数据。着重点不同,导致设计要求不同,数据表一旦形成,就不会因为一个特定的应用而进行调整,它必须服务于整个企业,因此,这种结构是许多不同应用之间平衡的选择;而使用对象可以针对具体应用进行设计,将业务行为放入对象中,更能精确反映领域概念,保证业务规则的真正逻辑一致地实现

虽然OOAD很不错,但分析和设计之间常常落差很大,甚至是分裂的。分析阶段的成果不能顺利导入设计阶段,设计阶段引入太多细节而歪曲了分析的宗旨。分析和设计分裂的根本原因是它们导向目标不同,分析人员的目标是从需求领域收集基本概念,是面向需求的,而设计人员则不同,他们负责用代码实现这些概念,因此必须指明能在项目中使用编程工具构建的组件,这些组件必须能够在目标环境(比如java)中有效执行,并能够正确解决应用程序出现的问题。条条大路通罗马,分析人员负责指出罗马方向,而设计人员负责找出通往罗马的某条道路,但是技术细节有时会让这个过程中产生绕路和不必要的复杂性,甚至走错方向,南辕北辙


DDD

程序员其实不是在编写代码,面是在摸索业务领域知识

这是很多程序员的真实写照,尤其复杂业务,为什么呢?

很多时候你会遇到这样的情况:一个函数写了几百行,里面的if-else写了一大堆,计算各种业务规则。另一个人接手之后,分析了好几天,才把业务逻辑彻底理清楚。

这个问题从表面来看,是代码写的不规范,要重构,把一个几百行的函数拆成一个个小的函数。从根本上来讲,就是“重要逻辑”隐藏在代码里面,没有“显性”的表达出来

这只是一个函数,推而广之,到类、到模块、到系统,是同样的道理,比如:

业务流程隐藏在多个对象的复杂调用关系里面;
某个业务核心概念没有提取出来,其职责分摊到了其他几个实体里面;
系统耦合,职责边界不清

所以,建模的本质就是把“重要的东西进行显性化,并进而把这些显性化的构造块,互相串联起来,组成一个体系”

而DDD就是为了解决这些痛点,DDD建模思想不同于以往的面向对象分析设计思想,建模和代码之间还存在落差,无法平滑衔接。它将分析和设计完美结合起来,通过引入上下文的特殊性,将项目的真正业务背景和集成复杂性引入设计建模阶段,虽然增加了设计的复杂性,但也提高 了设计的实用性。

领域驱动设计当然不是架构方法,也并非设计模式。准确地说,它其实是“一种思维方式,也是一组优先任务,它旨在加速那些必须处理复杂领域的软件项目的开发”。领域驱动设计贯穿了整个软件开发的生命周期,包括对需求的分析、建模、架构、设计,甚至最终的编码实现,乃至对编码的测试与重构。

领域驱动设计强调领域模型的重要性,并通过模型驱动设计来保障领域模型与程序设计的一致。从业务需求中提炼出统一语言(Ubiquitous Language),再基于统一语言建立领域模型;这个领域模型会指导着程序设计以及编码实现;最后,又通过重构来发现隐式概念,并运用设计模式改进设计与开发质量。这个过程如下图所示:

这个过程是一个覆盖软件全生命周期的设计闭环,每个环节的输出都可以作为下一个环节的输入,而在其中扮演重要指导作用的则是“领域模型”。这个设计闭环是一个螺旋式的迭代设计过程,领域模型会在这个迭代过程中逐渐演进,在保证模型完整性与正确性的同时,具有新鲜的活力,使得领域模型能够始终如一的贯穿领域驱动设计过程、阐释着领域逻辑、指导着程序设计、验证着编码质量。

如果仔细审视这个设计闭环,会发现在针对问题域和业务期望提炼统一语言,并通过统一语言进行领域建模时,可能会面临高复杂度的挑战。这是因为对于一个复杂的软件系统而言,我们要处理的问题域实在太庞大了。在为问题域寻求解决方案时,需要从宏观层次划分不同业务关注点的子领域,然后再深入到子领域中从微观层次对领域进行建模。宏观层次是战略的层面,微观层次是战术的层面,只有将战略设计与战术设计结合起来,才是完整的领域驱动设计

公众号:码农戏码
欢迎关注微信公众号『码农戏码』