vlambda博客
学习文章列表

领域驱动设计核心思想

WEB流行之前,模型驱动设计(DDD)就已经存在并获得广泛认可。随着微服务的流行,作为珠联璧合的好搭档,DDD又被频繁的提起和推崇,而实际上它一直都在那里。

 

()什么是DDD

 

软件的根本目标是赋能业务领域,软件必须要与所服务的领域和谐相处,服务、支撑、增进、驱动业务发展。软件脱胎于领域,最终也会融入领域,成为领域的一部分,以电商网站和电商业务为例,很容易理解这个结论。

 

软件如何与领域和谐相处呢?让软件成为领域的映射(Reflection),软件要具现领域中重要的核心概念和元素、以及它们之间的关系,即:软件需要对领域建模。

 

领域模型是对领域的一个严密组织的和选择性抽象的知识,是领域在人头脑中形成的概念的蓝图。但它不是一副具体的图,而是哪幅图要极力传递的思想,用精心设计的代码、清晰表达的文字、形式化的规范描述都能达到此目的。

 

领域驱动设计(Domain Driven Design:DDD) 是一种基于领域建模思想进行软件设计和开发的方法DDD由一系列的准则和设计方法(如模式)构成,能大幅提升软件开发过程的能力,用一种可维持的方式完成对领域复杂问题的建模和实现。

 

模型是对领域的一种映射,是通过某种手段对现实世界形成的一致的和简化的抽象,“一致”要求映射的规则要简单,“简化”要求映射的结果要简单。简单指相对于现实世界足够简单,但相对于要解决问题要足够灵活。

 

 

模型就如这张影子图片所展示,虽然逼真,但并不是现实世界自身。人对现实世界的认知,就是通过某种映射得到的,如眼睛所就是光线反射投影。

 

针对某个现实世界或问题,不同的映射方法和观察视角会产生不同的模型。选择模型的依据是能更真实地反映领域和更简单高效地解决需求问题。胜出的模型不仅准确对领域问题建模,也易于用软件开发技术实现。

 

()主要特征

 

DDD奉行一系列重要的准则,如下所示,这些准则是DDD所提倡的设计模式和工具的思想来源。

领域驱动设计核心思想 


 

领域模型

 

领域是世界上的某些东西(something),领域模型是遵循“第一性原理”或“本质论”哲学,面向领域特定问题,用于反映领域运行根本规律的知识。

 

其特征是共享概念模型的形式化规范描述”(源自本体(ontology)的定义,用在这里很贴切),包括四个要点。

“共享”指知识得到共同认可,表现为公认的术语集合。

“概念化"指对事物的描述表示成一组抽象概念。

“明确性”指全部的术语、属性等都有明确的定义。

“形式化”指能够被计算机处理。

 

良好领域知识是良好软件设计的前提,领域模型是领域知识的集中体现与重要形式。领域模型需要被精心地设计和组织,持续地优化完善。领域模型在包括领域专家在内的建设团队得到深刻理解和完全共识,且应该与具体实现无关。它能被用于问题分析、需求理解、团队沟通、软件设计与实现等,贯穿于全部软件建设活动,它是DDD方法的根基。

 

统一语言

 

领域专家与软件团队一起,共同努力建设高质量的软件,前提是需要高质量的沟通。高质量的沟通是准确捕获客户需求、客户尽早对软件方案充分理解和确认、团队成员紧密协作、知识共享、预期管理等一系列活动成功的关键因素。

 

软件专家脑子里全是类、方法、算法、继承、多态、设计模式等概念,而领域专家头脑中是截然不同的东西,沟通需要一套共同理解的语言,即统一语言(Ubiquitous Language)。这套语言既不是某位领域专家的语言,更不可能是软件语言,毕竟领域专家对软件一无所知,关键他们还是合作的甲方。统一语言应该是基于领域模型的语言,这是DDD的重要原则

 

统一语言是更接近于“领域”的语言,是领域的模型化定义后的规范术语,在软件生命周期中被广泛统一使用,包括且不限于每个人(领域专家、产品经理、架构师、开发人员等)、每个过程(需求、设计、测试、开发、维护等)和每种形式(语言、文字、图表、文档、架构、设计、代码等)。

 

1统一语言、领域模型和领域知识都被用于领域表达和理解,它们之间是一致的,重叠部分必须是相同的。语言侧重于术语(名词和动词),模型比语言更规范和完整,包括概念(名词)/行为(动词)/关系(结构)/符号(抽象定义)/规则/约束等,知识的内容则更宽泛一些。

2领域专家不限于某个角色或岗位,这里把所有些能帮助团队了解领域的人称为领域专家。

 

双向传递

 

领域知识不是由领域专家到产品经理、到架构师、到开发人员等单向传递,而应该是双向传递,由团队共同制定和完善的。

 

瀑布开发方法和敏捷开发方法,业务知识传递是单向的,如下图所示。

 

 

领域驱动设计核心思想

 

单向传递意味着假设前面环节的工作是正确的,能满足后续工作的需要,且接手者能正确理解前序的工作成果,这显然是不科学的。这样做有助于职能分工(包括外包合作),但不利于软件的设计与开发。

单向传递意味着前面工作的瑕疵被传递到下游,并被持续积累和放大。

单向传递意味着前面工作得不到后续工作的及时反馈,错误得不到及时纠正,直至测试甚至上线后方能发现。错误发现的越早纠正就越快且越容易,纠正需要付出的劳动成本、时间成本及机会成本越低。

让软件系统运转的是开发阶段产出的代码,单向传递意味着无法保证代码来自于正确的领域逻辑映射。不恰当的逻辑映射可能来自于不一致的概念、不一致的逻辑分解、与领域无关的逻辑混杂等。后果是代码难以理解和修改,为软件系统后续维护升级埋下巨大隐患。

单向传递意味着前面工作一旦阶段结束便会停止,没有动力和机制持续完善更新,容易成为一次性工作,造成大量浪费。

 

DDD要求知识向前传递和向后反馈双向进行。知识向前(实现一端)传递时,需要“确认”是否得到完全地接收、准确地理解、正确地实现(映射)。向后反馈,用于对前面工作的不足进行纠正、优化及补充,“验证”当前工作切实落实前面工作的目标

 

持续完善

 

统一语言、领域模型的建立不止于某一阶段的工作,更不是一次性工作。经验看,基础成果主要由领域专家和软件专家早期阶段共同建立,但完善和优化需要持续存在于整个软件生命周期。职责也不限于领域专家和软件专家,可以是建设团队的任何人。

 

为了对领域模型和软件应用持续完善,首先要持续完善建设团队对领域的认知,这需要有效的“双向沟通”和完善的“统一语言”。

 

通过对业务规范学习,基于重要名词(class)和动词(method)建立的粗浅模型只是开始。为了获得领域深入洞察,需要深入思考、丰富实践和聪明才华,还需要对模型做持续的重构。模型重构通常比代码重构更困难,除了简单的新概念、新关系、新方法引入,或者它们的细微调整,也会出现一些重大突破,如思考和看待模型方法的改变。

 

模型重构完善的契机有很多,包括

通过领域资料学习,发现新的知识和洞察。

通过领域中“约束”、“处理”、“规范”等内容进行“明确化”,补全模型中的规则和行为。

发现模型逻辑不易实现、模型不够灵活。

发现存在"互相矛盾",不管是与已有的逻辑、新的需求、还是不同人理解冲突。

发现实现逻辑拙笨难懂、不够清晰,可能是结构不合理,或存在隐含和遗失的概念。

 

软件设计人员、开发人员对模型承担重要的责任,代码是建设团队最终且最有价值的产出,代码是领域模型到实现逻辑的映射。早期的分析模型着重于业务逻辑的完整性和合理性,对实现逻辑考虑不多例如某些设计原则、性能考虑等,通常也缺少实现相关经验。对模型的优化需要把实现实践融合进来,保持优化后的模型与实现代码一致。开发人员通过对已有代码进行阅读,获得的领域知识应该与领域模型是一致的,软件维护升级时容易保持代码模型一致、不易被腐化。

 

代码的重构要在模型上体现出来,反之亦然。软件的升级与迭代,领域模型需要同步进行,始终保持与代码一致的简单映射关系。实践中,保持模型与代码一致并不容易。需要建设团队的坚守,也需要实践经验和工具支持。代码重构工具、完善的自动化测试案例、持续集成与持续交付工具是非常有效的。

 

实现分离

 

通常,软件应用的大部分代码与领域模型无关,常见的有数据库访问、网络和文件访问、外部系统或服务访问、界面展示和用户交互,以及安全、日志、可靠性等方面的代码。

 

常见的错误是,把领域无关代码错误地嵌入到领域模型代码中,或者领域模型代码(全部或部分)被错误地嵌入到领域无关代码中(UI),领域模型代码与其他代码混杂在一起导致领域模型被腐化,至少从代码层面看是这样。对模型代码或其他代码的修改变得困难复杂,逻辑互相影响导致局部逻辑优化影响范围不再可控。

 

这种错误之所以常见是因为项目规模比较小时,这样做操作上更简单;另一个原因是职责划分导致,开发人员在自己熟悉或能接触到的代码进行BUG修改。但对于中等及以上规模的系统,这样做就是在饮鸩止渴。

 

将模型代码从其他代码中分离出来,模型逻辑不再掺杂无关代码,让模型逻辑简单、清晰、独立地进行演进优化。实际上,模块化、分层、职责分离等是软件设计的通用准则,并非DDD独自推崇。

 

子域细分

 

由于巨大的工作量或多种职能划分,大的复杂应用通常需要多个团队或小组共同承担。这种情况下,维护一个包括所有内容的统一完整模型不现实也不必要。解决的办法就是把整个系统划分为多个子领域,每个子领域各自分别建立自有的模型,且分别进行实现和演进

 

所有子域模型按照一定的职责关系,共同构成完整的模型地图。相关联的子域模型之间通过某种适合的形式进行交互(通常借助Evans提到的某种Context Map技术)。

 

划分每个子域的范围和边界的核心思想还是“高内聚、低耦合”,一致的概念、组织分工(Conway定律)等因素。另一种细分领域的方法是从一个大的领域中分离成一个核心领域和多个通用领域(Generic Sub-domains),避免一个领域模型持续膨胀。精炼后的模型更利于工作开展,核心更小且更有价值,优先完成对核心领域的建设,核心则可以灵活安排例如外包。

 

()核心思想

 

DDD作为一种软件设计和开发的方法,是基于理论和经验提供的指导和建议,并不是软件建设的强制规范。其基础原则是研发团队的智慧应该聚焦于用户从事领域的重要问题(而不是架构或编码技术),这一点永远不会过时。

 

DDD所遵循的一些核心理念是非常先进的和有效的。包括

“领域模型”来自于第一性原理或本质论的哲学。

“双向传递”和“统一语言”是组织高效沟通协作的基础,借助众人的力量达成目标。

“持续完善”来自于人对事物的认知需要不断完善和进步。

“实现分”和“子域细分”来自于“分而治之和系统论”思想。

 

“领域模型”与“实现分离”结合在一起,是一种非常有效的复杂问题解决方法。通过模型捕获事物运行的根本逻辑,通过把辅助部分(DB/网络/UI)从根本逻辑分离降低整个问题的复杂度。子域细分则是更大更复杂的问题(领域)划分成一系列小问题(子域),然后借助组织团队的力量解决

 

“双向传递”、“统一语言”、“持续完善” 是建设团队学习领域、洞察领域、设计及实现领域的过程。包括领域专家在内的团队所有人,都需要大量地学习,没有谁能对领域了如指掌,“学习”是DDD的一个标签。持续完善模型和代码的过程,也是团队持续学习成长的过程,“既是人成就产品也是产品成就人”。

 

基于DDD设计的软件,理想状态应该是“设计即代码、代码即设计,代码即文档”。这绝不是乌托邦式的自欺欺人,而是设计要追求的方向。毕竟混乱拙笨的代码是不可能高质量的,高质量代码一定是清晰明确的。“设计即代码、代码即设计,代码即文档”并不是说代码将成为唯一的设计和文档,但会成为最重要、最详尽、最准确的设计和文档。质量越高的设计和代码将越少需要辅助文档进行说明。

 

()实践及挑战

 

DDD作为一种通用(企业应用领域)且先进的软件设计和开发方法,里面所提倡的准则和模式,软件专家在实践中或多或少地都使用过(可能并不知道自己用的是DDD)。

 

Eric Evans的经典之作《Domain-Driven Design: Tackling Complexity in the Heart of Software》一书中,介绍了许多具体的领域驱动设计模式(对应本文的“实现分离”特征),以及许多子域之间交互集成的设计模式(对应本文的“子域划分”特征)。

 

领域驱动设计模式集合如下图所示。

 

子域集成设计模式集合如下图所示。

 

 

Evans的这本书影响广泛,包括其他后续的DDD书籍,通常相关软件从业人员即使没读过这本书,也会从其他渠道途径略知一二。但软件研发是个非常复杂和专业的事情,“知一二”是不行的,做好需要真正贯彻其核心思想,灵活运用相关设计模式及经验,这才是挑战所在

 

在一次访谈中,Evans为落实DDD给出了一些实践建议,包括

坚持动手,建模者需要编码;

聚焦具体场景,抽象思考需要锚定到具体案例;

没有银弹,只用DDD解决哪些适合的问题,忽视范围之外的东东;

不停地实验和犯错,建模是一个创造性过程。

 

对于DDD专家而言,实践挑战在于尽力追求“真善美”。

“真”指正确性,能按照需求产生预期结果。

“美”指模型和代码的内在的高质量,清晰完整一致等一系列指标。

“善”指自发地行动,追求“真和美”,不能说客户未发现问题就可以不纠正,客户不懂模型代码就可以不追求内在质量,而把麻烦留给后续。

对于DDD而言,“美”和“真”是一体两面,如果不能对领域进行高质量地建模,代码就很难“准确落实业务规则”,软件应用的正确运行就得不到保障。

 

()DDD与微服务

 

二者侧重不同,微服务侧重于架构和组织,DDD侧重于设计及实现。微服务希望服务中心有明确的定位和边界,边界内独自演进,边界外基于契约交互,这恰恰是领域模型首要追求的。服务中心与领域模型能非常好地进行映射,如领域模型(子域模型)映射服务中心,模型方法映射服务方法。

 

如果说微服务是一个易于软件能力演进和服务容量扩展的架构,DDD就是一个设计实现此架构最有效的模式和方法。实践中,微服务是一个架构框架以及对应的基础设施(Spring Cloud)DDD是在此基础上进行软件设计及开发的设计模式及工具集。

 

无论如何,不要把微服务和DDD简化地看作一套工具集,虽然工具集很重要,但它们背后的核心思想和基本准则才是精华所在。如果说它们提供的工具帮助“正确地做事”,那么其背后的思想和准则帮助“做正确的事”。


()进一步知识

 

DSL

 

领域特定语言(Domain-Specific Languages)是在Evans之后DDD的新成果,有兴趣的朋友可以学习Martin Fowler的同名书籍(2010年出版)

 

 

有一些与DDD相似理念的软件设计方法和经验,接下来做一些简单说明,可以用于辅助DDD设计及开发工作。

 

本体论

 

本体论(Ontology)源于17世纪的一个哲学分支,关心的是客观事物存在的本质,内涵是“对世界上客观事物的系统描述,即存在论”。1998Studer给出本体的定义“本体是共享概念模型的形式化规范说明”。

 

本体论在许多领域(如知识图谱、逻辑推理等)得到应用,它也是一个非常有效的领域建模工具。DDD的成果集中在模型驱动的软件设计和开发,本体论则对领域模型自身进行充分研究,正好弥补了DDD在这方面的欠缺

 

MDA

 

Model Driven Architecture® (MDA®)是一种软件设计、开发及实现方法。2002年由OMG组织提出并逐步建立完善。MDA通过一套定义的模型标准规范(UMLMOFXMICWM)来设计平台无关的业务模型(业务功能和行为),这样业务模型和实现模型的底层技术平台就可以独立发展,自然可以通过模型进行跨平台系统集成及数据交换。

 

MDA一经提出便得到厂商争先跟进,也取得一些成果(标准及支持工具),但目前来看没有达到预期目标。笔者早年有过一些MDA的使用经验,并不认可"用一个本体(ontology )管理其他各个本体"的哲学,毕竟不同领域的业务逻辑太复杂了,通过一个标准来约束人们的行为会非常复杂且不满足需要(导致削足适履),最终消耗掉本应带来的好处甚至更糟糕。实际经验中,MDA工具对复杂业务显得力不从心(失去可读性的满屏幕密密麻麻的线框),但对于小规模项目或大项目的一个子域,被证明是十分有效的。

 

MDA对业务模型的重视,及业务模型与底层基础设施技术细节分离的哲学与DDD是相同的,相关工具及标准可用于支持DDD工作,例如UML就是一个非常广泛使用的建模工具。

 

CMMI-V&V

 

CMMI在软件开发领域具有很高的地位,包括大量最佳实践。其中Verification强调后续活动实现了前序活动的要求,Validation强调后续实现确实达成了前序活动的意图(目标)。二者结合一起,就是前后活动之间互相进行补充、验证、优化、完善。设计不仅正向实现需求,设计同时反向反馈完善需求。

 

Verification&ValidationDDD中模型与实现互相映射的理念相近,双向互相促进而不是单向传递,Verification&Validation的最佳实践可用于支持DDD工作

 

EA-Togaf

 

企业架构(Enterprise Architecture)IT规划领域很重要的一个工具,从整个企业视角规划IT架构,Togaf是企业架构中最流行的方法。

 

Togaf包括技术架构、数据架构、应用架构、业务架构四部分内容。数据应用业务架构与领域模型强相关,其相应方法可以为领域建模提供支持。有些行业或著名企业已经完成了数据架构参考模型的制定,它应该成为是领域模型的一个重要参考,这样领域建模会有一个比较好的基础,而不用从零开始


本文完)