关于软件架构:领域驱动设计DDD在百度爱番番的实践

42次阅读

共计 8847 个字符,预计需要花费 23 分钟才能阅读完成。

* 导读:畛域驱动设计(Domain Driven Design – DDD)起源于 2004 年 Eric Evans 出版《畛域驱动设计》,相比于在国外 IT 圈享有盛誉且卓有成效不同,国内 IT 圈理解 DDD 的人很少,落地实际的少之又少。最近几年随着微服务架构的遍及和中台的衰亡,DDD 也成了各大技术论坛和微信公众号文章里常常谈起的话题。*

DDD 的热度是起来了,但业界介绍 DDD 的材料大多偏实践,不足生产我的项目可借鉴的实践经验。因而大多人读了很多 DDD 资料后还是一脸懵,怎么掂量 DDD 带来的价值?老板能批准搞 DDD 吗?什么样的业务和团队适宜 DDD?DDD 跟互联网强调的小步快跑疾速迭代能搭吗?如果要实际 DDD 产研团队都要做些啥?研发写代码跟平时有什么不一样?本文联合百度爱番番产研团队在过来一年多经验的从摸索、推广到全面落地 DDD 的过程,尝试答复上述问题,力求给大家带来一些借鉴意义。

全文约 9500 字,预计浏览工夫 25 分钟。

1  初 心:以客户为核心,产 研团队如何高效交付需要

百度爱番番围绕营销拓客和销售提效帮忙企业收集、裁减、荡涤、造就、跟进和转化线索。一方面爱番番的业务特点是典型的企业级(ToB)业务,具备肯定的复杂度。业务对象多,单个业务对象提供的性能多,单个性能面向的场景多,业务对象之间组合进去的业务流程多。并且会随着交付的性能越多而变的越简单。另一方面产品处于爬坡阶段,性能须要疾速迭代交付到客户,从而疾速取得客户的反馈。产研团队在资源肯定的状况下如何高效交付更简单的需要成为了主要矛盾。

剖析以后阶段需要迭代过程中的问题,能够总结为以下几类问题:

  1. 业务逻辑不能从产品团队精准传递到研发团队,有时研发进行了一段时间开发才发现需要了解有偏差,导致须要从新跟产品经理探讨需要。
  2. 产品团队和研发团队对于业务复杂度没有的意识不对立,产品经理认为一个需要的开发不难,理当在较少工夫内开发结束。
  3. 研发团队面对需要增长和变动时,不足对业务逻辑的形象,往往开发一个须要点须要改变多处,容易出错且开发效率低,代码维护性差。
  4. 需要文档和代码逻辑不匹配,线上性能的业务逻辑为什么实现成那样没有根据可查,畛域常识得不到积淀,团队得不到可继续成长。

2  摸索:找到适宜的开发模式


上述问题集中体现在两个方面,一是产品属于企业应用类,性能自身简单,如何让产研团队疾速了解业务、疾速交付。二是如何让畛域常识可能比拟精确的失去开发实现,让代码有比拟好的可维护性。借鉴业内解决简单企业级软件的开发教训,加上局部团队成员已经有过 DDD 应用的教训,团队决定尝试使用 DDD 设计思维来领导产研团队的日常需要迭代。

2.1  DDD 是啥?

DDD 是一种围绕领域建模来解决简单业务交付的设计思维。读者无妨自问几个问题,什么是简单?什么是领域建模?

1. 什么是简单?如何了解简单?

简单可能是现状业务就简单,也可能是业务日渐演变成简单。简单来自规模在变,比方几个业务对象的逻辑不简单,几十上百个业务对象就会变得盘根错节。简单来自结构化有余,比方下图所示,结构化的中国结比非结构化的意大利面更有序、易于大脑了解。此外,一旦协同方多了,如何协同不同团队实现软件交付也是一种简单。

2. 什么是领域建模?

畛域模型跟技术毫无关系,而是为了更有结构化的拆解和表白业务逻辑。业务逻辑来自事实世界里的具体场景,波及可视画面、操作动作和流程。要精确表白业务逻辑须要先讲清楚每个概念是什么,再建设概念之间的分割,基于这些关系再组合出更多的流程。概念、分割、流程就是畛域模型。围绕畛域模型去表白业务时也自然而然地把技术实现细节拆散进来了。后续代码实现就是将业务架构映射到零碎架构的过程,当前业务架构调整了能疾速的调整技术架构。

3. DDD 中的畛域如何了解?

  • DDD 中示意业务逻辑的畛域概念是:实体、值对象、畛域服务、畛域事件。这意味着所有畛域逻辑都应该在这四种对象里,对立称为畛域模型对象,这将极大缩小业务逻辑的蔓延。
  • 引入聚合进一步封装实体和值对象,让畛域逻辑更内聚,起到边界爱护的作用。聚合的引入使得业务对象间的关联变少。如何设计聚合见上面实际局部。
  • 围绕聚合的操作引入工厂和资源库。工厂负责简单聚合的创立,资源库负责聚合的加载、增加、批改、删除。聚合内的实体状态变更通过畛域事件来推动。
  • 引入应用服务,对畛域逻辑编排、封装。供下层接口层调用。一个应用服务就是一次编排,一次编排就是一个用户用例。

4. DDD 畛域概念具体解释和举例

2.2  DDD 如何发展?

DDD 蕴含策略设计、战术设计、技术实现三个局部。策略设计侧重于高层次、宏观下来划分限界上下文,而战术设计则关注应用建模工具来细化上下文,通过畛域模型来表白业务。技术实现次要通过分层架构来隔离畛域模型代表的业务逻辑和技术细节。一个整体过程大抵包含:宏观划分各畛域 → 畛域内划分限界上下文,定义上下文之间的关系 → 上下文内剖析业务,辨认畛域概念,定义适合的畛域概念 → 通过分层架构实现编码,并验证畛域模型的合理性,必要时从新回到后面步骤重构畛域模型。

1. 策略设计

策略设计是团队领导层或业务负责人关怀的,该步骤须要针对产品愿景、业务要解决的问题域,布局外围域、通用域、撑持域,做适合的资源投入。

1. 什么是畛域和限界上下文?

畛域代表事实世界的特定问题和解决方案的汇合,比方销售畛域、营销畛域。DDD 里的限界上下文(Bouded Context)是对畛域的软件实现,比方线索零碎、商机零碎就是销售畛域内的限界上下文。限界上下文定义了解决方案的显著边界,边界里的每一个畛域概念,包含畛域概念内的属性和行为都有非凡含意。出了限界上下文这个边界这层含意就不复存在。

2. 如何划分限界上下文?

1:依据相关性做归类。个别是优先思考性能相关性而不是语义相关性,比方创立订单和领取订单都是订单语义,但性能相差比拟大,应该划分为两个限界上下文。

2:依据团队粒度做裁剪、依据技术特点做裁剪。一些通用的技术性能应该尽可能归拢到一个限界上下文,比方每个业务限界上下文都有监控,但监控能力应该归拢到监控限界上下文。

3. BC 与微服务什么关系?

微服务是蕴含高度相干性能的一个开发部署单元,有本人的技术自治性包含技术选型、弹性扩缩容、公布上线频率等,有本人的业务演变自治性。BC 是依据畛域逻辑的内聚状况造成的一个整体。一个微服务能够蕴含一个或多个 BC,到底蕴含几个?须要依据团队大小、BC 复杂度和技术个性来定。

2. 战术设计

DDD 设计思维里领域建模是最外围的一步,该阶段次要指标是提炼和定义出畛域模型和之间的关系。

1. 领域建模

建模就是设计的过程,建模的过程就是梳理、走查业务逻辑,拆解为要解决的问题和波及的业务场景、业务流程、业务概念,在这个过程中造成对应的畛域概念。

如果团队对于业务比拟生疏适宜采纳事件风暴办法进行梳理;如果团队对业务比拟相熟,如果业务流程绝对简略,则能够采纳四色建模法进行业务梳理。采纳这些剖析业务的办法能够保障产研团队对业务逻辑的了解在一个程度上。

2. 业务逻辑的显性表白

在实现了实体和值对象的设计后,有的时候会发现有些概念其实在畛域上是存在的,但设计和代码里没有 Class 来体现,可能仅仅是一个根本类型参数加上散落的对该参数的判断测验逻辑,这个时候还须要思考应该把这个概念显性化,定义专门的 Class 并蕴含相应逻辑,入出参以相应 Class 为类型。凡是业务代码逻辑蕴含了一堆 if-else,这时候须要思考尽可能给这段逻辑建模成一个畛域概念。

比方 CRM 零碎里判断一条线索是否为推广线索须要看线索的渠道属性是否来自推广平台,那么比拟好的形式是这段逻辑用 ” 推广线索 ” 这个概念来显性表白,而不是吞没在代码里不容易了解和保护。

3. 对立语言

为了解决业务逻辑连接的问题引入了对立语言。每个业务名词的含意具备明确的定义,产品和研发都统一认识。没有对立语言的沟通重大不足效率。比方 CRM 线索的概念,没有对立语言的时候每个人的了解不一样,有的人了解为有过征询记录的访客是线索,有的人了解为留下过联系方式的访客是线索,有的人了解为有购买志愿的访客是线索等等。

有了对立语言形容,每个概念就有了明确定义,能够节俭十分大的沟通交流老本。并且这个概念也同样利用在相干的需要文档、设计文档、代码编写中。每个概念从引入到日常交换,从需要文档到代码实现都有了统一的表白,代码实现和需要形容的真实度高,可了解性和可维护性就变好了。

3. 技术实现

1. 分层架构

为了让代码实现围绕畛域模型发展,尽量升高业务代码和纯技术选型代码的耦合,DDD 引入了分层架构。确保了最外围的畛域层不依赖其余层,反过来让畛域之外的代码依赖畛域代码,升高了技术升级带来的影响。

2. DDD 框架

框架内定义不同畛域概念须要实现的接口,比方实现了聚合根接口的实体类就成为了聚合的根实体。定义了异样治理标准,不同的分层应该抛出什么类型的异样。定义了数据拜访的资源库接口等等。

3. 畛域事件

畛域事件是对畛域内产生的流动进行的建模,即聚合内的实体状态变动的一个载体。DDD 提倡限界上下文间尽量解耦,尽可能应用公布订阅畛域事件的合作模式进行上下游解耦。

2.3  DDD vs 数据模型驱动

传统的业务开发模式里,研发受到关系型数据库设计范式、ER 图等影响深远,在做软件具体设计过程中往往先想到如何设计对应的表构造,由此倒推出业务逻辑代码该如何组织。这就是典型的数据模型驱动设计,或者叫面向数据表设计编程。数据模型设计关注的是数据存储,数据尽量不要冗余,管制表数量不收缩,更多思考数据的扩展性,比方新加一个字段尽量不要在几张表都加,能用一个字段表白就不必两个字段。

这样的思维跟 DDD 是相同的,DDD 优先思考畛域概念的业务语义表白,具备独立业务概念的货色会尽量形象成一个内聚的畛域对象。畛域对象不仅仅有属性,还有该有的行为。

因而,基于数据模型驱动的设计后果往往是:

1. 业务逻辑代码十分过程式,畛域实体只蕴含一堆属性,只是数据表的映射,没有业务行为。也就是常说的只有 getter 和 setter 办法的贫血对象。十分不足畛域概念的表白,业务逻辑散乱。比方值对象的设计在 DDD 里是一个类,在数据模型设计里往往是其余类的几个属性。

2. 聚合是 DDD 最小的复用单元,粒度更粗。数据模型设计里畛域实体的数量跟表数量一一对应,数据表是最小的复用单元,粒度太细。导致业务逻辑对应的实现类须要拜访很多的畛域实体,实现类之间的调用关系发散而盘根错节。下图是贫血模型和 DDD 富血模型的区别。

3. 数据表的关系表白很受限,具备主从关系的表之间很难看出主从。在 DDD 里聚合和聚合内的实体、值对象之间的关系在代码层面有显示的表白。

当然,DDD 思维里不是说不必思考数据表设计,而是要优先思考畛域概念的辨认和建模。表设计须要服务于畛域模型的设计,是技术实现的细节。因而明确 DDD 和数据模型驱动设计的区别反过来能更好地了解 DDD。

3  实际:案列剖析


3.1  业务背景

以爱番番业务中 ” 线索 ” 性能举例,线索治理性能特地多,有创立、荡涤、调配、打标签、跟进、回收、退回和转化等十几个治理动作。仅线索创立就分为手工录入创立、文件导入创立、营销零碎的后盾主动创立、开放平台创立,创立还分为单个创立和批量创立等等。线索这个对象跟其余对象比方客户、商机等联动组合进去很多场景和流程。

3.2  布局阶段

布局阶段须要思考产品愿景和服务蓝图,须要划分出产品的外围畛域,撑持畛域,通用畛域。如果从 0 到 1 开发产品的话布局阶段须要做很多的工作,比方开发一个 CRM 产品须要思考产品愿景和服务蓝图,须要聚焦到哪些业务畛域,是售前、售中还是售后?售前还能够细分为营销畛域还是销售畛域等等。百度爱番番致力打造易用的、灵便可配的线索管家性能。因而销售畛域的线索性能天然是外围模块。须要提供什么线索性能?须要通过分析阶段来拆解。

3.3  分析阶段

分析阶段是基于业务流程和功能分析出具体的业务对象,不同的业务对象归属划分到限界上下文。因为线索性能简单,团队对于线索性能认知不一,有必要让相干人员一起采纳事件风暴办法来剖析和梳理业务。事件风暴认为事件流很⼤水平上反映了事实业务逻辑,参加人员基于畛域事件产生的工夫线,把事件的前因后果逐渐开掘进去。整个过程蕴含辨认畛域事件、决策命令、畛域名词三个步骤。通过尝试答复这几个问题:这个业务波及的零碎产生了什么变动?变动由哪个角色通过什么形式触发的?零碎变动产生了哪些后果?

基于上述步骤,领域专家和相干人员针对线索业务进行事件风暴的后果为:

事件风暴要害图例:

事件风暴实际过程的几点 tips:

  1. 事件流简直等同业务逻辑,以此来斟酌业务逻辑的严密性,有果必有因。
  2. 紧扣事件因素:事件、规定、名词、命令、角色。
  3. 命名:紧扣业务,不参杂技术元素,警觉应用泛泛的词汇,尽可能地打消命名的⼆义性。
  4. 优先关注 happy-path 即失常门路,聚焦外围畛域里的门路。
  5. 事件风暴不是欲速不达,放弃迭代更新。

基于事件风暴的后果,须要把畛域名词和规定等划分到适合的限界上下文。依据后面介绍的如何划分限界上下文的办法,线索相干性能划分为几个限界上下文适合呢?这个时候须要看业务逻辑的复杂程度,还要联合团队规模大小。因为线索性能蕴含很多业务逻辑,线索归集和创立、线索的调配、线索的跟进等都能够成为一个独立的限界上下文。定义好限界上下文后还须要定义不同限界上下文的协作关系。个别状况下如果业务容许的状况尽量抉择通过畛域事件来合作。依据《畛域驱动设计》所述常见的协作关系还包含凋谢主机服务(即通过裸露接口)、共享内核、防腐层等 9 种。微服务架构下的限界上下文之间的关系比拟常见的有畛域事件、凋谢主机服务、防腐层等。

3.4  设计阶段

设计阶段就是把分析阶段产出的畛域名词,畛域事件,决策命令用 DDD 畛域概念来承接,并细化每个畛域概念的数据和行为。这也是一种领域建模的过程。

倡议的建模过程是:

  1. 业务需要的剖析过程自上而下,由业务流程,到用户用例,到畛域模型。而设计过程是自下而上的。从畛域元素设计开始,最初才是应用服务的编排。
  2. 倡议设计优先级是先值对象 → 再实体 → 再聚合 → 再畛域服务→ 最初是应用服务,优先思考畛域是否应该为值对象,其次是否为实体,划分出聚合。不属于实体或值对象中的畛域行为放到畛域服务,须要协调聚合的畛域行为设计为畛域服务或者应用服务。
  3. 任何业务代码逻辑优先映射到原子性的畛域模型,比方值对象、实体、畛域事件、资源库接口、内部适配接口,其次再映射到组合性畛域模型,比方畛域服务、应用服务。

建模过程中常常会被问到的问题有:

1 值对象能够定义本人的行为吗?

能够,尽可能把属于值对象本人的行为放到值对象里。比方联系方式定义成一个值对象,如果它的校验只依赖本身数据,那校验行为应该属于在联系方式这个值对象。

2 聚合该设计为多大粒度?

聚合设计要尽量小,如果一个实体不是根实体,但同时须要被外界间接拜访到,那么这个实体不应该在这个聚合中,应该独立成新的聚合。

3 一个聚合如何拜访另外一个聚合?

只有聚合根才是拜访聚合边界的惟一入口,因而一个聚合须要通过另一个的聚合的聚合根来拜访它,聚合根能够了解为聚合的根实体的 Id。

4 应用服务与畛域服务的区别?

畛域服务处在分层架构的畛域层,是畛域逻辑的一部分。应用服务处在应用层,负责畛域模型的编排。当业务逻辑不属于任何聚合时,应该思考用畛域服务来封装这些逻辑。比方断定订单是否反复,应该属于订单限界上下文的一种业务逻辑,订单聚合自身不能判断是否反复,因而订单判重应该定义为畛域服务。

5 应用服务能够间接调用聚合和资源库吗?

能够,可被应用服务编排的对象包含聚合、资源库、畛域服务和适配接口。

6 畛域事件内容是蕴含整个聚合里的信息,还是身份标识信息(订阅方再通过独自接口依据标识进行查问),还是只蕴含聚合中一些特定的信息?

畛域事件是用于跟其余聚合合作,事件内容不应是整个聚合,而是通过裁剪的特定信息。

依据分析阶段的产出后果,须要把畛域名词、规定映射到畛域模型。次要几个线索相干畛域对象如下图示:

3.5  实现阶段

传统的接口 - 逻辑 - 数据拜访三层架构里,业务逻辑层的 XxxServiceImpl 类是个上帝类,往往通过过程式业务逻辑实现。前几行代码做校验,接下来做数据类型转换,而后是业务解决逻辑的代码,两头穿插着通过接口或者 dao 获取更多的数据;拿到数据后,又是类型转换代码,而后接着一段业务逻辑代码,最初可能还要落库、公布音讯等等。这样的代码参杂了太多不同的代码,十分难以保护。

业界自从 DDD 的分层架构提出后陆续呈现过洋葱架构、六边形架构、整洁架构等,其指标都是为了拆散业务和技术,保障畛域模型的纯正性。下图是联合业界架构实际后定制的分层架构,具备以下几个特点:

  1. 接口层负责对外裸露各种协定的接口比方 http、tcp,转换成应用服务能意识的协定。
  2. 外围的畛域层不依赖其余层,通过资源库包下的接口定义做到依赖倒置,接口参数不能体现具体技术实现细节,畛域模型里的实现逻辑只依赖接口。这样做到对畛域逻辑的一层防腐。本层里以聚合为单位搁置代码,便于当前零碎拆分,以聚合为单位。
  3. 应用层定义应用服务,一个接口对应业务场景的一个用例。此外应用层还能够解决横切面事务比方启动数据库事务。
  4. 基础设施层实现资源库的理论实现,以及畛域层定义的其余接口的实现如对外部服务的拜访,畛域事件公布到音讯队列中间件等。
  5. 分层架构还定义了每层的我的项目包构造,不同的畛域概念和数据对象相应的命名标准。

实现阶段常常会被问到的问题有:

1

每层应该用什么类型数据对象承载和传递数据?

如下面分层架构图所示,接口层和应用服务层用 DTO 对象传递数据,畛域层只能见到畛域对象即聚合、实体 Entity 和值对象 VO。应用服务层负责把 DTO 对象转换成畛域对象传输到畛域层。基础设施层用 PO 示意数据表,跟畛域层调用时须要把 PO 和畛域对象互相做转换。

2

repository 和 dao 的区别?

聚合设计要尽量小,如果一个实体不是根实体,但同时须要被外界间接拜访到,那么这个实体不应该在这个聚合中,应该独立成新的聚合。

3

畛域事件的公布应该在畛域层还是应用层?

只有不会毁坏各层的依赖程序,在哪公布都行。取决于畛域事件定义在哪层?个别举荐定义在畛域层的聚合内。当然即使在应用层公布事件也不会毁坏依赖方向。因而聚合、畛域服务、应用服务都能够公布事件。

3.6  代码示例

以 java 代码为例,DDD 骨架代码蕴含了分层架构,每层就是一个 maven pom 我的项目,依据用处定义好了多层包构造,每个畛域对象和数据传输对象都有具体的命名形式。基于自研的 ddd-framework 标准了不同畛域对象须要实现的接口或继承于特定的基类。

总之,尽可能做到了能依据需要文档里的业务逻辑很快找到代码所在之处,让不同的代码待在应该待的分层和包上面。团队成员开玩笑说,当初开发业务代码就像在做填空题,简略直白。

3.7  收益

目前百度爱番番的新服务默认都会在合乎 DDD 架构的骨架代码根底上开发,存量的外围模块也进行过 DDD 革新。全面实施 DDD 后产研团队指标更对齐,合作效率更高,播种了很多收益,包含但不限于以下几点:

  1. 产研团队协同老本升高,畛域常识失去积攒和积淀。对立语言的应用和保护极大进步了大家对齐的老本。
  2. 业务语义失去显性表白,业务逻辑内聚可复用水平进步,防止了很多散弹式批改和发散式批改。一个需要不必改多个中央,多个需要也不必几个研发集中改同一个中央。
  3. 限界上下文的划分从业务合理性登程,进而微服务的划分会更正当,缩小了团队间的耦合和不必要的协同代价。
  4. 接口数量精简、可控。因为业务代码聚焦畛域模型,逻辑内聚,复用性高,急剧缩小了接口数量,升高接口保护老本。
  5. 通过预约义好的脚手架创立合乎 DDD 标准的代码骨架,进步了新服务开发的效率。
  6. 代码可读性高,不是代码作者也能疾速定位到代码地位,代码设计可能失去传承,可维护性也进步了。
  7. 新人相熟新业务和新代码的速度极大进步,业务和技术常识的转移代价减低。

3.8  实际总结

从需要到交付的一次典型软件开发流程包含收集提炼需要、需要剖析、业务 & 技术设计、代码实现、测试上线等环节。如何联合软件开发流程,每个流程阶段具体要做什么、怎么做,特地在编码落地阶段该有什么保障措施?爱番番产研团队在落地过程中逐渐总结出了一套卓有成效的 DDD 施行指南。包含布局、剖析、设计到实现四个阶段对应的办法和产出等施行要点。


4  结语:必由之路、没有银弹


DDD 一方面应用分而治之的思维,引入划分畛域、限界上下文、模块分层、划分聚合在不同档次、不同粒度来升高问题的复杂度。另一方主张聚焦畛域逻辑,通过不同伎俩来缩小业务和技术的耦合。因而 DDD 只是大部分软件设计思维一种,软件设计的实质都是为了高内聚低耦合。然而 DDD 并不是万能的,不是所有业务开发场景都适宜用 DDD。有些简略业务场景不应用 DDD 反而更失当。因为 DDD 有较高的学习门槛,须要整个团队造成统一认识和协同,须要相应的编码标准和架构落地。因而学习和落地 DDD 时要时刻记住本人的出发点是为了应答当初或者未来的简单业务畛域而来。不用太拘泥于某些点是否恪守了 DDD 准则,如果感觉用了 DDD 会比没有用好一点点,也值得迈出这一步。

爱番番产研团队始终秉持“以客户为核心”的理念,使用 DDD 设计思维构建对立的业务模型,实现业务性能的复用和交融。随着爱番番业务的倒退,咱们置信 DDD 带来的收益会更大。今后咱们会从产品、技术、流程和组织方面继续关注能无效解决软件工程复杂性问题的办法。

本期作者|飞邪

在百度爱番番次要负责销售域和连通域的技术,长期关注技术团队如何高效服务产品团队等研发效力话题,善于 ToB 企业级利用的布局和落地。

招聘信息

无论你是后端,前端,大数据还是算法,这里有若干职位在等你,欢送投递简历,爱番番业务部期待你的退出!

浏览原文:畛域驱动设计(DDD)在百度爱番番的实际

更多干货、内推福利,欢送关注同名公众号「百度 Geek 说」~

正文完
 0