导读:领域驱动设计(Domain Driven Design - DDD)起源于2004年Eric Evans出版《领域驱动设计》,相比于在国外IT圈享有盛誉且行之有效不一样,国内IT圈了解DDD的人不多,落地实践的少之又少。最近几年随着微服务架构的普及和中台的兴起,DDD也成了各大技术论坛和微信公众号文章里常常谈起的话题。DDD的热度是起来了,但业界介绍DDD的资料大多偏理论,缺少生产项目可借鉴的实践经验。所以大多人读了不少DDD材料后仍是一脸懵,怎么衡量DDD带来的价值?老板能赞成搞DDD吗?什么样的业务和团队适合DDD?DDD跟互联网强调的小步快跑快速迭代能搭吗?若是要实践DDD产研团队都要作些啥?研发写代码跟平时有什么不同?本文结合百度爱番番产研团队在过去一年多经历的从探索、推广到全面落地DDD的过程,尝试回答上述问题,力求给你们带来一些借鉴意义。java
全文约9500字,预计阅读时间25分钟。数据库
1 初心:以客户为中心,产研团队如何高效交付需求
编程
百度爱番番围绕营销拓客和销售提效帮助企业收集、扩充、清洗、培养、跟进和转化线索。一方面爱番番的业务特色是典型的企业级(ToB)业务,具备必定的复杂度。业务对象多,单个业务对象提供的功能多,单个功能面向的场景多,业务对象之间组合出来的业务流程多。而且会随着交付的功能越多而变的越复杂。另外一方面产品处于爬坡阶段,功能须要快速迭代交付到客户,从而快速得到客户的反馈。产研团队在资源必定的状况下如何高效交付更复杂的需求成为了主要矛盾。
分析当前阶段需求迭代过程当中的问题,能够总结为如下几类问题:
微信
上述问题集中体如今两个方面,一是产品属于企业应用类,功能自己复杂,如何让产研团队快速理解业务、快速交付。二是如何让领域知识可以比较准确的获得开发实现,让代码有比较好的可维护性。借鉴业内处理复杂企业级软件的开发经验,加上部分团队成员曾经有过DDD使用的经验,团队决定尝试运用DDD设计思想来指导产研团队的平常需求迭代。
网络
DDD是一种围绕领域建模来解决复杂业务交付的设计思想。读者不妨自问几个问题,什么是复杂?什么是领域建模?架构
1. 什么是复杂?如何理解复杂?
复杂多是现状业务就复杂,也多是业务日渐演变成复杂。复杂来自规模在变,好比几个业务对象的逻辑不复杂,几十上百个业务对象就会变得错综复杂。复杂来自结构化不足,好比下图所示,结构化的中国结比非结构化的意大利面更有序、易于大脑理解。此外,一旦协同方多了,如何协同不一样团队完成软件交付也是一种复杂。
2. 什么是领域建模?
app
领域模型跟技术毫无关系,而是为了更有结构化的拆解和表达业务逻辑。业务逻辑来自现实世界里的具体场景,涉及可视画面、操做动做和流程。要准确表达业务逻辑须要先讲清楚每一个概念是什么,再创建概念之间的联系,基于这些关系再组合出更多的流程。概念、联系、流程就是领域模型。围绕领域模型去表达业务时也天然而然地把技术实现细节分离出去了。后续代码实现就是将业务架构映射到系统架构的过程,之后业务架构调整了能快速的调整技术架构。
框架
3. DDD中的领域如何理解?
数据库设计
4. DDD领域概念详细解释和举例
maven
名称 | 定义 | 举例 |
实体 | 实体通常对应业务对象,具备业务属性和业务行为 | 线索是个实体,线索的状态会随着跟进活动的推动随时变化,须要根据惟一标识来追踪变化 |
值对象 | 值对象主要是属性集合,对实体的状态、特征进行业务语义的描述 | 线索上的联系方式信息是值对象,包含联系方式类型和联系方式内容,不须要惟一标识去追踪联系方式的变化过程,总体替换新联系方式 |
聚合 | 聚合是由业务和逻辑紧密关联的实体和值对象组成的,是数据修改和持久化的基本单元 | 线索是个聚合,线索实体是该聚合的根实体,状态信息、联系方式信息等是附属聚合的值对象 |
资源库 | 资源库是对资源访问的抽象。不局限于数据库、文件、网络存储。接口须要不依赖于具体的数据存储和ORM实现框架 | 线索这个聚合的访问经过线索资源库提供,资源库的实现因技术选型不一样而不一样,能够是数据库、文件等 |
领域事件 | 表示领域中所发生的重要事件,事件发生后一般会致使进一步的业务操做,或者在系统其余地方引发反应 | 线索建立后会产生线索已建立领域事件,后续的线索分配服务、打标签服务能够监听该事件启动相应操做 |
领域服务 | 领域服务没有任何属性或数据,只是一个领域行为或动做,不适合放在任何聚合内的逻辑行为 | 线索的查重行为属于领域服务,单个线索自身无法完成查重行为 |
应用服务 | 应用服务对应到一个具体业务场景,经过编排聚合、资源库、领域事件、外部适配接口、领域服务来完成 | 线索建立这个场景对应线索建立应用服务,该服务会编排线索查重、线索聚合建立、线索资源库建立、线索已建立领域事件发送等 |
DDD包含战略设计、战术设计、技术实现三个部分。战略设计侧重于高层次、宏观上去划分限界上下文,而战术设计则关注使用建模工具来细化上下文,经过领域模型来表达业务。技术实现主要经过分层架构来隔离领域模型表明的业务逻辑和技术细节。一个总体过程大体包括:宏观划分各领域 → 领域内划分限界上下文,定义上下文之间的关系 → 上下文内分析业务,识别领域概念,定义合适的领域概念 → 经过分层架构实现编码,并验证领域模型的合理性,必要时从新回到前面步骤重构领域模型。
1. 战略设计
战略设计是团队领导层或业务负责人关心的,该步骤须要针对产品愿景、业务要解决的问题域,规划核心域、通用域、支撑域,作合适的资源投入。
什么是领域和限界上下文?
领域表明现实世界的特定问题和解决方案的集合,好比销售领域、营销领域。DDD里的限界上下文(Bouded Context)是对领域的软件实现,好比线索系统、商机系统就是销售领域内的限界上下文。限界上下文定义了解决方案的明显边界,边界里的每个领域概念,包括领域概念内的属性和行为都有特殊含义。出了限界上下文这个边界这层含义就不复存在。
如何划分限界上下文?
1:根据相关性作归类。通常是优先考虑功能相关性而不是语义相关性,好比建立订单和支付订单都是订单语义,但功能相差比较大,应该划分为两个限界上下文。
2:根据团队粒度作裁剪、根据技术特色作裁剪。一些通用的技术功能应该尽量归拢到一个限界上下文,好比每一个业务限界上下文都有监控,但监控能力应该归拢到监控限界上下文。
BC与微服务什么关系?
微服务是包含高度相关功能的一个开发部署单元,有本身的技术自治性包括技术选型、弹性扩缩容、发布上线频率等,有本身的业务演变自治性。BC是根据领域逻辑的内聚状况造成的一个总体。一个微服务能够包含一个或多个BC,到底包含几个?须要根据团队大小、BC复杂度和技术特性来定。
2. 战术设计
DDD设计思想里领域建模是最核心的一步,该阶段主要目标是提炼和定义出领域模型和之间的关系。
领域建模
建模就是设计的过程,建模的过程就是梳理、走查业务逻辑,拆解为要解决的问题和涉及的业务场景、业务流程、业务概念,在这个过程当中造成对应的领域概念。
若是团队对于业务比较陌生适合采用事件风暴方法进行梳理;若是团队对业务比较熟悉,若是业务流程相对简单,则能够采用四色建模法进行业务梳理。采用这些分析业务的方法能够保证产研团队对业务逻辑的理解在一个水平上。
业务逻辑的显性表达
在完成了实体和值对象的设计后,有的时候会发现有些概念其实在领域上是存在的,但设计和代码里没有Class来体现,可能仅仅是一个基本类型参数加上散落的对该参数的判断检验逻辑,这个时候还须要思考应该把这个概念显性化,定义专门的Class并包含相应逻辑,入出参以相应Class为类型。但凡业务代码逻辑包含了一堆if-else,这时候须要考虑尽量给这段逻辑建模成一个领域概念。
好比CRM系统里判断一条线索是否为推广线索须要看线索的渠道属性是否来自推广平台,那么比较好的方式是这段逻辑用"推广线索"这个概念来显性表达,而不是淹没在代码里不容易理解和维护。
统一语言
为了解决业务逻辑衔接的问题引入了统一语言。每一个业务名词的含义具备明确的定义,产品和研发都统一认识。没有统一语言的沟通严重缺少效率。好比CRM线索的概念,没有统一语言的时候每一个人的理解不同,有的人理解为有过咨询记录的访客是线索,有的人理解为留下过联系方式的访客是线索,有的人理解为有购买意愿的访客是线索等等。
有了统一语言描述,每一个概念就有了明肯定义,能够节省很是大的沟通交流成本。而且这个概念也一样应用在相关的需求文档、设计文档、代码编写中。每一个概念从引入到平常交流,从需求文档到代码实现都有了一致的表达,代码实现和需求描述的真实度高,可理解性和可维护性就变好了。
3. 技术实现
分层架构
为了让代码实现围绕领域模型开展,尽可能下降业务代码和纯技术选型代码的耦合,DDD引入了分层架构。确保了最核心的领域层不依赖其余层,反过来让领域以外的代码依赖领域代码,下降了技术升级带来的影响。
DDD框架
框架内定义不一样领域概念须要实现的接口,好比实现了聚合根接口的实体类就成为了聚合的根实体。定义了异常管理规范,不一样的分层应该抛出什么类型的异常。定义了数据访问的资源库接口等等。
领域事件
领域事件是对领域内发生的活动进行的建模,即聚合内的实体状态变化的一个载体。DDD提倡限界上下文间尽可能解耦,尽量使用发布订阅领域事件的协做模式进行上下游解耦。
传统的业务开发模式里,研发受到关系型数据库设计范式、ER图等影响深远,在作软件详细设计过程当中每每先想到如何设计对应的表结构,由此倒推出业务逻辑代码该如何组织。这就是典型的数据模型驱动设计,或者叫面向数据表设计编程。数据模型设计关注的是数据存储,数据尽可能不要冗余,控制表数量不膨胀,更多考虑数据的扩展性,好比新加一个字段尽可能不要在几张表都加,能用一个字段表达就不用两个字段。
这样的思惟跟DDD是相反的,DDD优先考虑领域概念的业务语义表达,具备独立业务概念的东西会尽可能抽象成一个内聚的领域对象。领域对象不只仅有属性,还有该有的行为。
所以,基于数据模型驱动的设计结果每每是:
1. 业务逻辑代码很是过程式,领域实体只包含一堆属性,只是数据表的映射,没有业务行为。也就是常说的只有getter和setter方法的贫血对象。很是缺少领域概念的表达,业务逻辑散乱。好比值对象的设计在DDD里是一个类,在数据模型设计里每每是其余类的几个属性。
2. 聚合是DDD最小的复用单元,粒度更粗。数据模型设计里领域实体的数量跟表数量一一对应,数据表是最小的复用单元,粒度太细。致使业务逻辑对应的实现类须要访问不少的领域实体,实现类之间的调用关系发散而错综复杂。下图是贫血模型和DDD富血模型的区别。
3. 数据表的关系表达很受限,具备主从关系的表之间很难看出主从。在DDD里聚合和聚合内的实体、值对象之间的关系在代码层面有显示的表达。
固然,DDD思想里不是说不用考虑数据表设计,而是要优先考虑领域概念的识别和建模。表设计须要服务于领域模型的设计,是技术实现的细节。所以明白DDD和数据模型驱动设计的区别反过来能更好地理解DDD。
以爱番番业务中"线索"功能举例,线索管理功能特别多,有建立、清洗、分配、打标签、跟进、回收、退回和转化等十几个管理动做。仅线索建立就分为手工录入建立、文件导入建立、营销系统的后台自动建立、开放平台建立,建立还分为单个建立和批量建立等等。线索这个对象跟其余对象好比客户、商机等联动组合出来不少场景和流程。
规划阶段须要考虑产品愿景和服务蓝图,须要划分出产品的核心领域,支撑领域,通用领域。若是从0到1开发产品的话规划阶段须要作不少的工做,好比开发一个CRM产品须要考虑产品愿景和服务蓝图,须要聚焦到哪些业务领域,是售前、售中仍是售后?售前还能够细分为营销领域仍是销售领域等等。百度爱番番致力打造易用的、灵活可配的线索管家功能。所以销售领域的线索功能天然是核心模块。须要提供什么线索功能?须要经过分析阶段来拆解。
3.3 分析阶段
分析阶段是基于业务流程和功能分析出具体的业务对象,不一样的业务对象归属划分到限界上下文。由于线索功能复杂,团队对于线索功能认知不一,有必要让相关人员一块儿采用事件风暴方法来分析和梳理业务。事件风暴认为事件流很⼤程度上反映了现实业务逻辑,参与人员基于领域事件发生的时间线,把事件的来龙去脉逐步挖掘出来。整个过程包含识别领域事件、决策命令、领域名词三个步骤。经过尝试回答这几个问题:这个业务涉及的系统产生了什么变化?变化由哪一个角色经过什么方式触发的?系统变化产生了哪些结果?
基于上述步骤,领域专家和相关人员针对线索业务进行事件风暴的结果为:
事件风暴关键图例:
事件风暴实践过程的几点tips:
基于事件风暴的结果,须要把领域名词和规则等划分到合适的限界上下文。根据前面介绍的如何划分限界上下文的方法,线索相关功能划分为几个限界上下文合适呢?这个时候须要看业务逻辑的复杂程度,还要结合团队规模大小。因为线索功能包含不少业务逻辑,线索归集和建立、线索的分配、线索的跟进等均可以成为一个独立的限界上下文。定义好限界上下文后还须要定义不一样限界上下文的协做关系。通常状况下若是业务容许的状况尽可能选择经过领域事件来协做。根据《领域驱动设计》所述常见的协做关系还包括开放主机服务(即经过暴露接口)、共享内核、防腐层等9种。微服务架构下的限界上下文之间的关系比较常见的有领域事件、开放主机服务、防腐层等。
设计阶段就是把分析阶段产出的领域名词,领域事件,决策命令用DDD领域概念来承接,并细化每一个领域概念的数据和行为。这也是一种领域建模的过程。
建议的建模过程是:
建模过程当中常常会被问到的问题有:
1 值对象能够定义本身的行为吗?
能够,尽量把属于值对象本身的行为放到值对象里。好比联系方式定义成一个值对象,若是它的校验只依赖自身数据,那校验行为应该属于在联系方式这个值对象。
2 聚合该设计为多大粒度?
聚合设计要尽可能小,若是一个实体不是根实体,但同时须要被外界直接访问到,那么这个实体不该该在这个聚合中,应该独立成新的聚合。
3 一个聚合如何访问另一个聚合?
只有聚合根才是访问聚合边界的惟一入口,所以一个聚合须要经过另外一个的聚合的聚合根来访问它,聚合根能够理解为聚合的根实体的Id。
4 应用服务与领域服务的区别?
领域服务处在分层架构的领域层,是领域逻辑的一部分。应用服务处在应用层,负责领域模型的编排。当业务逻辑不属于任何聚合时,应该考虑用领域服务来封装这些逻辑。好比断定订单是否重复,应该属于订单限界上下文的一种业务逻辑,订单聚合自己不能判断是否重复,所以订单判重应该定义为领域服务。
5 应用服务能够直接调用聚合和资源库吗?
能够,可被应用服务编排的对象包括聚合、资源库、领域服务和适配接口。
6 领域事件内容是包含整个聚合里的信息,仍是身份标识信息(订阅方再经过单独接口根据标识进行查询),仍是只包含聚合中一些特定的信息?
领域事件是用于跟其余聚合协做,事件内容不该是整个聚合,而是通过裁剪的特定信息。
根据分析阶段的产出结果,须要把领域名词、规则映射到领域模型。主要几个线索相关领域对象以下图示:
传统的接口-逻辑-数据访问三层架构里,业务逻辑层的XxxServiceImpl类是个上帝类,每每经过过程式业务逻辑实现。前几行代码作校验,接下来作数据类型转换,而后是业务处理逻辑的代码,中间穿插着经过接口或者dao获取更多的数据;拿到数据后,又是类型转换代码,而后接着一段业务逻辑代码,最后可能还要落库、发布消息等等。这样的代码参杂了太多不一样的代码,很是难以维护。
业界自从DDD的分层架构提出后陆续出现过洋葱架构、六边形架构、整洁架构等,其目标都是为了分离业务和技术,保证领域模型的纯粹性。下图是结合业界架构实践后定制的分层架构,具备如下几个特色:
实现阶段常常会被问到的问题有:
1 每层应该用什么类型数据对象承载和传递数据?
如上面分层架构图所示,接口层和应用服务层用DTO对象传递数据,领域层只能见到领域对象即聚合、实体Entity和值对象VO。应用服务层负责把DTO对象转换成领域对象传输到领域层。基础设施层用PO表示数据表,跟领域层调用时须要把PO和领域对象相互作转换。
2 repository和dao的区别?
聚合设计要尽可能小,若是一个实体不是根实体,但同时须要被外界直接访问到,那么这个实体不该该在这个聚合中,应该独立成新的聚合。
3 领域事件的发布应该在领域层仍是应用层?
只要不会破坏各层的依赖顺序,在哪发布都行。取决于领域事件定义在哪层?通常推荐定义在领域层的聚合内。固然即使在应用层发布事件也不会破坏依赖方向。所以聚合、领域服务、应用服务均可以发布事件。
以java代码为例,DDD骨架代码包含了分层架构,每层就是一个maven pom项目,根据用途定义好了多层包结构,每一个领域对象和数据传输对象都有具体的命名方式。基于自研的ddd-framework规范了不一样领域对象须要实现的接口或继承于特定的基类。
总之,尽量作到了能根据需求文档里的业务逻辑很快找到代码所在之处,让不一样的代码待在应该待的分层和包下面。团队成员开玩笑说,如今开发业务代码就像在作填空题,简单直白。
目前百度爱番番的新服务默认都会在符合DDD架构的骨架代码基础上开发,存量的核心模块也进行过DDD改造。全面实施DDD后产研团队目标更对齐,协做效率更高,收获了不少收益,包括但不限于如下几点:
从需求到交付的一次典型软件开发流程包括收集提炼需求、需求分析、业务&技术设计、代码实现、测试上线等环节。如何结合软件开发流程,每一个流程阶段具体要作什么、怎么作,特别在编码落地阶段该有什么保障措施?爱番番产研团队在落地过程当中逐步总结出了一套行之有效的DDD实施指南。包括规划、分析、设计到实现四个阶段对应的方法和产出等实施要点。
DDD一方面使用分而治之的思想,引入划分领域、限界上下文、模块分层、划分聚合在不一样层次、不一样粒度来下降问题的复杂度。另外一方主张聚焦领域逻辑,经过不一样手段来减小业务和技术的耦合。所以DDD只是大部分软件设计思想一种,软件设计的本质都是为了高内聚低耦合。可是DDD并非万能的,不是全部业务开发场景都适合用DDD。有些简单业务场景不使用DDD反而更恰当。由于DDD有较高的学习门槛,须要整个团队造成统一认识和协同,须要相应的编码规范和架构落地。所以学习和落地DDD时要时刻记住本身的出发点是为了应对如今或者未来的复杂业务领域而来。没必要太拘泥于某些点是否遵照了DDD原则,若是以为用了DDD会比没有用好一点点,也值得迈出这一步。
爱番番产研团队始终秉持“以客户为中心”的理念,运用DDD设计思想构建统一的业务模型,实现业务功能的复用和融合。随着爱番番业务的发展,咱们相信DDD带来的收益会更大。从此咱们会从产品、技术、流程和组织方面持续关注能有效解决软件工程复杂性问题的方法。
本期做者|飞邪
在百度爱番番主要负责销售域和连通域的技术,长期关注技术团队如何高效服务产品团队等研发效能话题,擅长ToB企业级应用的规划和落地。
阅读原文:领域驱动设计(DDD)在百度爱番番的实践
更多干货、内推福利,欢迎关注同名公众号「百度Geek说」~