2. 什么是领域建模/strong>
领域模型跟技术毫无关系,而是为了更有结构化的拆解和表达业务逻辑。业务逻辑来自现实世界里的具体场景,涉及可视画面、操作动作和流程。要准确表达业务逻辑需要先讲清楚每个概念是什么,再建立概念之间的联系,基于这些关系再组合出更多的流程。概念、联系、流程就是领域模型。围绕领域模型去表达业务时也自然而然地把技术实现细节分离出去了。后续代码实现就是将业务架构映射到系统架构的过程,以后业务架构调整了能快速的调整技术架构。
3. DDD中的领域如何理解/strong>
-
DDD中表示业务逻辑的领域概念是:实体、值对象、领域服务、领域事件。这意味着所有领域逻辑都应该在这四种对象里,统一称为领域模型对象,这将极大减少业务逻辑的蔓延。
-
引入聚合进一步封装实体和值对象,让领域逻辑更内聚,起到边界保护的作用。聚合的引入使得业务对象间的关联变少。如何设计聚合见下面实践部分。
-
围绕聚合的操作引入工厂和资源库。工厂负责复杂聚合的创建,资源库负责聚合的加载、添加、修改、删除。聚合内的实体状态变更通过领域事件来推动。
-
引入应用服务,对领域逻辑编排、封装。供上层接口层调用。一个应用服务就是一次编排,一次编排就是一个用户用例。
4. DDD领域概念详细解释和举例
3. 数据表的关系表达很受限,具有主从关系的表之间很难看出主从。在DDD里聚合和聚合内的实体、值对象之间的关系在代码层面有显示的表达。
当然,DDD思想里不是说不用考虑数据表设计,而是要优先考虑领域概念的识别和建模。表设计需要服务于领域模型的设计,是技术实现的细节。因此明白DDD和数据模型驱动设计的区别反过来能更好地理解DDD。
3 实践:案列分析
3.1 业务背景
以爱番番业务中”线索”功能举例,线索管理功能特别多,有创建、清洗、分配、打标签、跟进、回收、退回和转化等十几个管理动作。仅线索创建就分为手工录入创建、文件导入创建、营销系统的后台自动创建、开放平台创建,创建还分为单个创建和批量创建等等。线索这个对象跟其他对象比如客户、商机等联动组合出来很多场景和流程。
3.2 规划阶段
规划阶段需要考虑产品愿景和服务蓝图,需要划分出产品的核心领域,支撑领域,通用领域。如果从0到1开发产品的话规划阶段需要做很多的工作,比如开发一个CRM产品需要考虑产品愿景和服务蓝图,需要聚焦到哪些业务领域,是售前、售中还是售后前还可以细分为营销领域还是销售领域等等。百度爱番番致力打造易用的、灵活可配的线索管家功能。因此销售领域的线索功能自然是核心模块。需要提供什么线索功能要通过分析阶段来拆解。
3.3 分析阶段
分析阶段是基于业务流程和功能分析出具体的业务对象,不同的业务对象归属划分到限界上下文。因为线索功能复杂,团队对于线索功能认知不一,有必要让相关人员一起采用事件风暴方法来分析和梳理业务。事件风暴认为事件流很度上反映了现实业务逻辑,参与人员基于领域事件发生的时间线,把事件的前因后果逐步挖掘出来。整个过程包含识别领域事件、决策命令、领域名词三个步骤。通过尝试回答这几个问题:这个业务涉及的系统产生了什么变化化由哪个角色通过什么方式触发的统变化产生了哪些结果/p>
基于上述步骤,领域专家和相关人员针对线索业务进行事件风暴的结果为:
事件风暴实践过程的几点tips:
-
事件流几乎等同业务逻辑,以此来推敲业务逻辑的严密性,有果必有因。
-
紧扣事件要素:事件、规则、名词、命令、角色。
-
命名:紧扣业务,不参杂技术元素,警惕使用泛泛的词汇,尽可能地消除命名的性。
-
优先关注happy-path即正常路径,聚焦核心领域里的路径。
-
事件风暴不是一蹴而就,保持迭代更新。
基于事件风暴的结果,需要把领域名词和规则等划分到合适的限界上下文。根据前面介绍的如何划分限界上下文的方法,线索相关功能划分为几个限界上下文合适呢个时候需要看业务逻辑的复杂程度,还要结合团队规模大小。由于线索功能包含很多业务逻辑,线索归集和创建、线索的分配、线索的跟进等都可以成为一个独立的限界上下文。定义好限界上下文后还需要定义不同限界上下文的协作关系。一般情况下如果业务允许的情况尽量选择通过领域事件来协作。根据《领域驱动设计》所述常见的协作关系还包括开放主机服务(即通过暴露接口)、共享内核、防腐层等9种。微服务架构下的限界上下文之间的关系比较常见的有领域事件、开放主机服务、防腐层等。
3.4 设计阶段
设计阶段就是把分析阶段产出的领域名词,领域事件,决策命令用DDD领域概念来承接,并细化每个领域概念的数据和行为。这也是一种领域建模的过程。
建议的建模过程是:
-
业务需求的分析过程自上而下,由业务流程,到用户用例,到领域模型。而设计过程是自下而上的。从领域元素设计开始,最后才是应用服务的编排。
-
建议设计优先级是先值对象 → 再实体 → 再聚合 → 再领域服务→ 最后是应用服务,优先考虑领域是否应该为值对象,其次是否为实体,划分出聚合。不属于实体或值对象中的领域行为放到领域服务,需要协调聚合的领域行为设计为领域服务或者应用服务。
-
任何业务代码逻辑优先映射到原子性的领域模型,比如值对象、实体、领域事件、资源库接口、外部适配接口,其次再映射到组合性领域模型,比如领域服务、应用服务。
建模过程中经常会被问到的问题有:
1 值对象可以定义自己的行为吗/p>
可以,尽可能把属于值对象自己的行为放到值对象里。比如联系方式定义成一个值对象,如果它的校验只依赖自身数据,那校验行为应该属于在联系方式这个值对象。
2 聚合该设计为多大粒度/p>
聚合设计要尽量小,如果一个实体不是根实体,但同时需要被外界直接访问到,那么这个实体不应该在这个聚合中,应该独立成新的聚合。
3 一个聚合如何访问另外一个聚合/p>
只有聚合根才是访问聚合边界的唯一入口,因此一个聚合需要通过另一个的聚合的聚合根来访问它,聚合根可以理解为聚合的根实体的Id。
4 应用服务与领域服务的区别/p>
领域服务处在分层架构的领域层,是领域逻辑的一部分。应用服务处在应用层,负责领域模型的编排。当业务逻辑不属于任何聚合时,应该考虑用领域服务来封装这些逻辑。比如判定订单是否重复,应该属于订单限界上下文的一种业务逻辑,订单聚合本身不能判断是否重复,因此订单判重应该定义为领域服务。
5 应用服务可以直接调用聚合和资源库吗/p>
可以,可被应用服务编排的对象包括聚合、资源库、领域服务和适配接口。
6 领域事件内容是包含整个聚合里的信息,还是身份标识信息(订阅方再通过单独接口根据标识进行查询),还是只包含聚合中一些特定的信息/p>
领域事件是用于跟其他聚合协作,事件内容不应是整个聚合,而是经过裁剪的特定信息。
根据分析阶段的产出结果,需要把领域名词、规则映射到领域模型。主要几个线索相关领域对象如下图示:
实现阶段经常会被问到的问题有:
1
每层应该用什么类型数据对象承载和传递数据/p>
如上面分层架构图所示,接口层和应用服务层用DTO对象传递数据,领域层只能见到领域对象即聚合、实体Entity和值对象VO。应用服务层负责把DTO对象转换成领域对象传输到领域层。基础设施层用PO表示数据表,跟领域层调用时需要把PO和领域对象相互做转换。
2
repository和dao的区别/p>
聚合设计要尽量小,如果一个实体不是根实体,但同时需要被外界直接访问到,那么这个实体不应该在这个聚合中,应该独立成新的聚合。
3
领域事件的发布应该在领域层还是应用层/p>
只要不会破坏各层的依赖顺序,在哪发布都行。取决于领域事件定义在哪层般推荐定义在领域层的聚合内。当然即便在应用层发布事件也不会破坏依赖方向。因此聚合、领域服务、应用服务都可以发布事件。
3.6 代码示例
以java代码为例,DDD骨架代码包含了分层架构,每层就是一个maven pom项目,根据用途定义好了多层包结构,每个领域对象和数据传输对象都有具体的命名方式。基于自研的ddd-framework规范了不同领域对象需要实现的接口或继承于特定的基类。
总之,尽可能做到了能根据需求文档里的业务逻辑很快找到代码所在之处,让不同的代码待在应该待的分层和包下面。团队成员开玩笑说,现在开发业务代码就像在做填空题,简单直白。
4 结语:殊途同归、没有银弹
DDD一方面使用分而治之的思想,引入划分领域、限界上下文、模块分层、划分聚合在不同层次、不同粒度来降低问题的复杂度。另一方主张聚焦领域逻辑,通过不同手段来减少业务和技术的耦合。因此DDD只是大部分软件设计思想一种,软件设计的本质都是为了高内聚低耦合。但是DDD并不是万能的,不是所有业务开发场景都适合用DDD。有些简单业务场景不使用DDD反而更恰当。因为DDD有较高的学习门槛,需要整个团队形成统一认识和协同,需要相应的编码规范和架构落地。因此学习和落地DDD时要时刻记住自己的出发点是为了应对现在或者将来的复杂业务领域而来。不必太拘泥于某些点是否遵守了DDD原则,如果觉得用了DDD会比没有用好一点点,也值得迈出这一步。
爱番番产研团队始终秉持“以客户为中心”的理念,运用DDD设计思想构建统一的业务模型,实现业务功能的复用和融合。随着爱番番业务的发展,我们相信DDD带来的收益会更大。今后我们会从产品、技术、流程和组织方面持续关注能有效解决软件工程复杂性问题的方法。
在百度爱番番主要负责销售域和连通域的技术,长期关注技术团队如何高效服务产品团队等研发效能话题,擅长ToB企业级应用的规划和落地。
招聘信息
无论你是后端,前端 ,大数据还是算法,这里有若干职位在等你,欢迎投递简历, 爱番番业务部期待你的加入!
阅读原文:领域驱动设计(DDD)在百度爱番番的实践
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!