领域驱动设计的实现之路

2004年,当Eric Evans的那本《领域驱动设计——软件核心复杂性应对之道》(后文简称《领域驱动设计》)出版时,我还在念高中,接触到领域驱动设计(DDD)已经是8年后的事情了。那时,我正打算在软件开发之路上更进一步,经同事介绍,我开始接触DDD。

我想,多数有经验的程序开发者都应该听说过DDD,并且尝试过将其应用在自己的项目中。不知你是否遇到过这样的场景:你创建了一个资源库(Repository),但一段时间之后发现这个资源库和传统的DAO越来越像了,你开始反思自己的实现方式是正确的吗?或者,你创建了一个聚合,然后发现这个聚合是如此的庞大,它为什么引用了如此多的对象,难道又是我做错了吗?

其实你并不孤单,我相信多数同仁都曾遇到过相似的问题。前不久,我一个同事给我展示了他在2007年买的那本已经被他韦编三绝过的《领域驱动设计》,他告诉我,读过好几遍后,他依然不知道如何将DDD付诸实践。Eric那本书固然是好,无可否认,但是我们程序员总希望看到一些实际的例子能够切实将DDD落地以指导我们的日常开发。

于是,在Eric的书出版将近10年之后,我们有了Vaughn Vernon的《实现领域驱动设计》,作为该书的译者,我有幸通读了本书,受益匪浅,得到的结论是:好的软件就应该是DDD的。

就像在微电子领域有知识产权核(Intellectual Property)一样,DDD将一个软件系统的核心业务功能集中在一个核心域里面,其中包含了实体、值对象、领域服务、资源库和聚合等概念。在此基础上,DDD提出了一套完整的支撑这样的核心领域的基础设施。此时,DDD已经不再是“面向对象进阶”那么简单了,而是演变成了一个系统工程。

所谓领域,即是一个组织的业务开展方式,业务价值便体现在其中。长久以来,我们程序员都是很好的技术型思考者,我们总是擅长从技术的角度来解决项目问题。但是,一个软件系统是否真正可用是通过它所提供的业务价值体现出来的。因此,与其每天钻在那些永远也学不完的技术中,何不将我们的关注点向软件系统所提供的业务价值方向思考思考,这也正是DDD所试图解决的问题。

在DDD中,代码就是设计本身,你不再需要那些繁文缛节的并且永远也无法得到实时更新的设计文档。编码者与领域专家再也不需要翻译才能理解对方所表达的意思。

DDD有战略设计和战术设计之分。战略设计主要从高层“俯视”我们的软件系统,帮助我们精准地划分领域以及处理各个领域之间的关系;而战术设计则从技术实现的层面教会我们如何具体地实施DDD。

DDD之战略设计

需要指出的是,DDD绝非一套单纯的技术工具集,但是我所看到的很多程序员却的确是这么认为的,并且也是怀揣着这样的想法来使用DDD的。过于拘泥于技术上的实现将导致DDD-Lite。简单来讲,DDD-Lite将导致劣质的领域对象,因为我们忽略了DDD战略建模所带来的好处。

DDD的战略设计主要包括领域/子域、通用语言、限界上下文和架构风格等概念。

领域和子域(Domain/Subdomain)

既然是领域驱动设计,那么我们主要的关注点理所当然应该放在如何设计领域模型上,以及对领域模型的划分。

领域并不是多么高深的概念,比如,一个保险公司的领域中包含了保险单、理赔和再保险等概念;一个电商网站的领域包含了产品名录、订单、发票、库存和物流的概念。这里,我主要讲讲对领域的划分,即将一个大的领域划分成若干个子域。

在日常开发中,我们通常会将一个大型的软件系统拆分成若干个子系统。这种

划分有可能是基于架构方面的考虑,也有可能是基于基础设施的。但是在DDD中,我们对系统的划分是基于领域的,也即是基于业务的。

于是,问题也来了:首先,哪些概念应该建模在哪些子系统里面?我们可能会发现一个领域概念建模在子系统A中是可以的,而建模在子系统B中似乎也合乎情理。第二个问题是,各个子系统之间的应该如何集成?有人可能会说,这不简单得就像客户端调用服务端那么简单吗?问题在于,两个系统之间的集成涉及到基础设施和不同领域概念在两个系统之间的翻译,稍不注意,这些概念就会对我们精心创建好的领域模型造成污染。

如何解决?答案是:限界上下文和上下文映射图。

限界上下文(Bounded Context)

在一个领域/子域中,我们会创建一个概念上的领域边界,在这个边界中,任何领域对象都只表示特定于该边界内部的确切含义。这样边界便称为限界上下文。限界上下文和领域具有一对一的关系。

举个例子,同样是一本书,在出版阶段和出售阶段所表达的概念是不同的,出版阶段我们主要关注的是出版日期,字数,出版社和印刷厂等概念,而在出售阶段我们则主要关心价格,物流和发票等概念。我们应该怎么办呢,将所有这些概念放在单个Book对象中吗?这不是DDD的做法,DDD有限界上下文将这两个不同的概念区分开来。

从物理上讲,一个限界上下文最终可以是一个DLL(.NET)文件或者JAR(Java)文件,甚至可以是一个命名空间(比如Java的package)中的所有对象。但是,技术本身并不应该用来界分限界上下文。

将一个限界上下文中的所有概念,包括名词、动词和形容词全部集中在一起,我们便为该限界上下文创建了一套通用语言。通用语言是一个团队所有成员交流时所使用的语言,业务分析人员、编码人员和测试人员都应该直接通过通用语言进行交流。

对于上文中提到的各个子域之间的集成问题,其实也是限界上下文之间的集成问题。在集成时,我们主要关心的是领域模型和集成手段之间的关系。比如需要与一个REST资源集成,你需要提供基础设施(比如Spring 中的RestTemplate),但是这些设施并不是你核心领域模型的一部分,你应该怎么办呢?答案是防腐层,该层负责与外部服务提供方打交道,还负责将外部概念翻译成自己的核心领域能够理解的概念。当然,防腐层只是限界上下文之间众多集成方式的一种,另外还有共享内核、开放主机服务等,具体细节请参考《实现领域驱动设计》原书。限界上下文之间的集成关系也可以理解为是领域概念在不同上下文之间的映射关系,因此,限界上下文之间的集成也称为上下文映射图。

时间: 2024-08-01 10:32:58

领域驱动设计的实现之路的相关文章

基于事件驱动的DDD领域驱动设计框架分享(附源代码)

补充:现在再回过头来看这篇文章,感觉当初自己偏激了,呵呵.不过没有以前的我,怎么会有现在的我和现在的enode框架呢?发现自己进步了真好! 从去年10月份开始,学了几个月的领域驱动设计(Domain Driven Design,简称DDD).主要是学习领域驱动设计之父Eric Evans的名著:<Domain-driven design:领域驱动设计:软件核心复杂性应对之道>,以及另外一本Martin Flower的<企业应用架构模式>,学习到了不少关于如何组织业务逻辑方面的知识.

领域驱动设计(DDD)在美团点评业务系统的实践

前言 至少30年以前,一些软件设计人员就已经意识到领域建模和设计的重要性,并形成一种思潮,Eric Evans将其定义为领域驱动设计(Domain-Driven Design,简称DDD).在互联网开发"小步快跑,迭代试错"的大环境下,DDD似乎是一种比较"古老而缓慢"的思想. 然而,由于互联网公司也逐渐深入实体经济,业务日益复杂,我们在开发中也越来越多地遇到传统行业软件开发中所面临的问题.本文就先来讲一下这些问题,然后再尝试在实践中用DDD的思想来解决这些问题.

DDD领域驱动设计:聚合、实体、值对象

关于具体需求,请看前面的博文:DDD领域驱动设计实践篇之如何提取模型,下面是具体的实体.聚合.值对象的代码,不想多说什么是实体.聚合等概念,相信理论的东西大家已经知晓了.本人对DDD表示好奇,没有在真正项目实践过,甚至也没有看过真正的DDD实践的项目源码,处于极度纠结状态,甚至无法自拔,所以告诫DDD爱好者们,如果要在项目里面实践DDD,除非你对实体建模和领域职责非常了解(很多时候会纠结一些逻辑放哪里好,属于设计问题)以及你的团队水平都比较高认同DDD,否则请慎重...勿喷! 代码在后,请先看D

如何实现领域驱动设计

从Eric Evans写下经典名著Domain-Driven Design: Tackling Complexity in the Heart of Software至今,DDD刚好发展了十年的时间.它几乎成了开发复杂软件系统主要的领域设计方法,既是面向对象(组件)设计的补充,又超越了面向对象(组件)设计.DDD中提出的诸多概念如实体.值对象.聚合等,已经成为了耳熟能详的设计术语.DDD社区的发展也如火如荼,似乎并没有被层出不穷的设计思想所取代,相反,它仍在强劲地发展,吸收了许多新的概念与方法,

领域驱动设计的编码: 数据聚焦型开发的技巧

今年,Eric Evans 具有开创性的软件设计图书"Domain-Driven Design: Tackling Complexity in the Heart of Software"(领域驱动设计:软件核心复杂性应对之道) (Addison-Wesley Professional,2003 年,amzn.to/ffL1k)迎来了其出版十周年的纪念日.Evans 在本书中分享了其指导大型企业完成构建软件的过程的多年经验.他随后花了更长时间考虑 如何概括帮助这些项目获得成功的模式 -

领域驱动设计和开发实战

背景 领域驱动设计(DDD)的中心内容是如何将业务领域概念映射到软件工件中.大部分关于此主题的著作 和文章都以Eric Evans的书<领域驱动设计>为基础,主要从概念和设计的角度探讨领域建模和设计情况 .这些著作讨论实体.值对象.服务等DDD的主要内容,或者谈论通用语言.界定的上下文(Bounded Context)和防护层(Anti-Corruption Layer)这些的概念. 本文旨在从实践的角度探讨领域建模和设计,涉及如何着手处理领域模型并实际地实现它.我们将着 眼于技术主管和架构师

EntityFramework之领域驱动设计实践(九):储的实现:深入篇

早在年前的时候就已经在CSAI博客发表了上一篇文章:<仓储的实现:基础篇>.苦于日夜奔波于工作与生活之间,一直没有能够抽空继续探讨仓储的实现细节,也让很多关注EntityFramework和领域驱动设计的朋友们备感失望. 闲话不多说,现在继续考虑,如何让仓储的操作在相同的事物处理上下文中进行.DDD引入仓储模式,其目的之一就是能够通过仓储隐藏对象持久化的技术细节,使得领域模型变得更为"纯净".由此可见,仓储的实现是需要基础结构层的组件支持的,表现为对数据库的操作.在传统的关

EntityFramework之领域驱动设计实践(七):模型对象的生命周期

上文中已经提到了管理领域模型对象生命周期的两大角色,即工厂与仓储,并对工厂的EntityFramework实践作了详细的描述.本节主要介绍仓储的概念,由于仓储的内容比较多,我将在接下来的两节中具体讲解仓储的架构设计与实践经验. 仓储(Repository),顾名思义,就是一个仓库,这个仓库保存着领域模型的实体对象.在业务处理的过程中,我们有可能需要把正在参与处理过程的对象保存到仓储中,也有可能会从仓储中读取需要的实体对象,抑或将对象直接从仓储中删除.上文也用一张简要的状态图描述了仓储在管理领域模

EntityFramework之领域驱动设计实践(六):模型对象的生命周期

首先应该认识到,是对象就有生命周期.这一点无论在面向对象语言还是在领域驱动设计中都适用.在领域驱动设计中,模型对象生命周期可以简要地用下图表示: 通过上图可以看到,对象通过工厂从无到有创建,创建后处于活动状态,此时可以参与领域层的业务处理:对象通过仓储实现持久化(也就是我们常说的"保存")和重建(也就是我们常说的"读取").内存中的对象通过析构而消亡,处于持久化状态的对象则通过仓储进行撤销(也就是我们常说的"删除").整个状态转换过程非常清晰.