如何实现领域驱动设计

从Eric Evans写下经典名著Domain-Driven Design: Tackling Complexity in the Heart of Software至今,DDD刚好发展了十年的时间。它几乎成了开发复杂软件系统主要的领域设计方法,既是面向对象(组件)设计的补充,又超越了面向对象(组件)设计。DDD中提出的诸多概念如实体、值对象、聚合等,已经成为了耳熟能详的设计术语。DDD社区的发展也如火如荼,似乎并没有被层出不穷的设计思想所取代,相反,它仍在强劲地发展,吸收了许多新的概念与方法,例如函数式编程思想、Event Source、CQRS等。然而,就我个人所观察到的情况来看,许多项目虽然号称应用了DDD设计,但主要都停留在Eric所谓的“战术设计”层面。即使是战术层面,依旧有许多程序员没有弄明白实体与值对象之间的区别,不知道该怎么定义聚合以及聚合根,更谈不上合理地划分上下文(Context)。

我不明白其中内含的真实原因,只能冒昧地揣测是否DDD显得高高在上?究其原因,会否还是Eric惹的祸,他的那本经典之作美则美矣,却显得有些不接地气?至少,我的阅读感受正是如此。虽然在之后,国内也引进了其他一些与DDD相关的著作,例如Jimmy Nilsson的著作《领域驱动设计与模式实战》。这些书好虽好,却并没有全面深入地阐述领域驱动设计,更谈不上完整地实践,直到Vaughn Vernon的《实现领域驱动设计(Implementing Domain-Driven Design)》的出现。

这本书首先吸引我的是书中的第2章至第4章。虽然书中内容几乎忠实地反映了Eric Evans的DDD理论,但作者却创造性地在一开始就着眼于DDD的战略性设计,包括领域(Domain)、子域(Subdomain)、受限上下文(Unbounded Context)、上下文映射(Context Map)以及架构。以第4章架构为例,书中对DDD经典的分层架构进行了深入探讨与分析,并颠覆性地提出将基础设施层(Infrastructure Layer)置于用户接口层(User Interface Layer)之上。最初读来,简直让我莫名惊诧,然而仔细思索,从依赖倒置原则的角度来分析,实在是合乎情理。坦白说,它彻底解决了之前一直纠缠在我心底的一个问题:若我们视实体为Repository以及数据访问的对象,应将实体置于哪一层?在DDD中,实体对象承担了领域业务行为,但同时又可能通过ORM与数据表产生映射。基础设施层的数据访问对象(即传统的DAO)需要调用这些实体对象。若它处于最底层,则会造成业务行为与基础设施的混合。若将实体与数据映射对象分离,既会造成对象之间的重复,又会导致不好的贫血对象。而将基础设施层放在分层架构的上端,非常巧妙地解决了这一问题。

我尤其喜欢本书引荐的由Corkburn提出的六边形(Hexagonal)架构。它完全突破了传统分层架构的窠臼,以独到的边界划分手法指导我们遵循关注点分离原则。该架构模式对端口(Port)与适配器(Adapter)的强调,使得我们可以在架构分析与设计时,更加关注系统之间的集成点,从而形成可视性极强的物理架构。这对于建立可伸缩的分布式架构尤有价值。我已在多个项目的架构设计中,运用了六边形架构模式,可谓收获颇丰。书中还提到了相对较新的RESTful架构,CQRS架构以及事件驱动的架构模式和网格分布式计算。该书的附录还进一步探讨了聚合对象与事件源的组合设计。这些内容有助于我们树立整体的DDD架构观,可以说弥补了Eric书中对这些内容的空白。

Vaughn Vernon是真正懂得写作的技术专家,他非常懂得如何“讨好”读者。翻开书,阅读第一章,你就会爱上它,爱上DDD。书中给出的例子实在太棒了。看看他对saveCustomer()方法的版本演进,你会幡然醒悟,原来代码应该这样写。领域对象是一位谨慎的保密者,他严格地谨守着自己的秘密,只把业务外部行为暴露给你,使得你可以读懂它,却不应该干扰它的内部实现。这正是DDD中通用语言的价值。身为一名技术人员,我们精通Java、C#、Scala、Ruby等等语言,却忘了在企业开发中,真正需要展现的其实是业务通用语言。写代码首先是与人交流,而不是机器。

本书的精彩章节有很多,几乎阅读每篇都会有感悟。但我个人认为,尤其彰显本书价值的是第8章与第10章。并非其他章节不够好,但相对于实体、值对象等容易理解的概念而言,聚合总是被人所误用,又或者让人茫然不知所措。第10章总结了非常实用的聚合设计原则。例如,在一致性边界之内对真正的不变量进行建模的原则;设计小聚合的原则;通过唯一标识引用其他聚合的原则。在边界之外使用最终一致性的原则。

至于第8章介绍的Domain Event,则是因为它不同于Eric的著作,将事件当做了与实体同等地位的头等公民。结合本书对事件驱动以及事件源的讲解,相信你在之后的领域建模时,会重视对领域事件的识别。若能正确地理解事件,则可以更好地掌握CQRS模式,并因地制宜地运用这一模式。

该书书名为Implementing(实现) Domain-Driven Design,就说明作者的意图是要让DDD真正落地。怎么做到?——上实例!书中给出的虽然是虚拟案例,却非常接近真实。作者甚至按照一种演进设计的方式深入浅出地介绍了这两个完整案例。最能够帮助人理解的是,他在讲授DDD时,还结合案例给出了之前欠佳的反面案例,并通过识别设计的坏味道,运用DDD方式对其进行改进。作者甚至创造了虚拟的场景,使得我们在阅读这些内容时,就好像真正参与了设计师的讨论,甚至能听到他们的唇枪舌剑,最后是DDD专家的总结陈词,真好比身临其境地加入了这个虚拟团队,一起学习,一起分享,共同成长。

坦白说,我在阅读本书时方才发觉自己在DDD上的浅薄无知。或许是自己的悟性不够,未能很好地理解Eric提出的DDD概念。阅读此书,让我有醍醐灌顶之感。现在,对于DDD,我已渐窥门径。若能多结合项目实践,定能登堂入室,甚至走得更远。

时间: 2024-10-28 19:32:01

如何实现领域驱动设计的相关文章

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

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

领域驱动设计的实现之路

2004年,当Eric Evans的那本<领域驱动设计--软件核心复杂性应对之道>(后文简称<领域驱动设计>)出版时,我还在念高中,接触到领域驱动设计(DDD)已经是8年后的事情了.那时,我正打算在软件开发之路上更进一步,经同事介绍,我开始接触DDD. 我想,多数有经验的程序开发者都应该听说过DDD,并且尝试过将其应用在自己的项目中.不知你是否遇到过这样的场景:你创建了一个资源库(Repository),但一段时间之后发现这个资源库和传统的DAO越来越像了,你开始反思自己的实现方式

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

今年,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之领域驱动设计实践(六):模型对象的生命周期

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

EntityFramework之领域驱动设计实践(五):聚合

聚合(Aggregate)是领域驱动设计中非常重要的一个概念.简单地说,聚合是这样一组领域对象(包括实体和值对象),这组领域对象联合起来表述一个完整的领域概念.比如,根据Eric Evans<领域驱动设计>一书中的例子,一辆车包含四个轮子,轮子离开"车"就毫无意义,此时这个联合体就是聚合,而"车"就是聚合根(Aggregate Root). 从实践中得知,并非领域模型中的每个实体都能够完整地表述一个明确的领域概念,就比如客户与送货地址的关系.假设在某个应

EntityFramework之领域驱动设计实践(三):一个简易的销售系统

案例:一个简易的销售系统 从现在开始,我们将以一个简易的销售系统为例,探讨EntityFramework在领域驱动设计上的应用.为了方便讨论,我们的销售系统非常简单,不会涉及客户存在多个收货地址的情况,也不会包含任何库存管理的内容.假设我们的系统只需要维护产品类型.产品以及客户信息,并能够帮客户下订单.跟踪订单状态,以及接受客户退货.从简单的分析我们大致可以了解到,这个系统将会有如下实体:客户.单据.产品及其类型.单据分为销售订单和退货单两种,每个单据可以有多个单据行(比如销售订单行和退货单行)