DDD是一套完善的系统设计方法,可以帮助我们在系统设计的过程中缕清思路,规范流程,降低系统建设的复杂度,同时DDD的领域建模过程也是团队成员之间形成系统通用语言、建立良好沟通机制的过程。
在电商中台研发团队内部,很早之前就对DDD有过分享和讨论,近期我们在做购车发票总库的迁移工作,经过分析,借此项目做一次DDD的落地实践,在时间和人员投入上都比较适宜。本篇文章就对我们的整个实践过程做一个总结,与大家一起学习和提升。
我们先来看一张DDD的经典知识体系结构图:
在整个发票总库DDD的实践过程中,我们也是按照战略设计和战术设计两个阶段进行:业务抽象的过程就是业务领域建模的过程,对应DDD的战略设计,而系统架构设计并落地的过程则对应DDD的战术设计。
在战略设计阶段,因为发票总库的业务逻辑相对比较清晰,也有助于我们采用事件风暴的方式,快速的找出了聚合、领域对象、领域类型,为构建领域模型奠定了基础,以下是我们梳理完成的发票域的对象清单:
从上图中可以看到,整个发票域我们只定义了发票这个聚合,同时它也是聚合根,在发票领域服务内部,还包含了排重规则、发票日志两个实体以及发票审核状态、业务类型、入库类型、应用唯一标识四个值对象,结合发票入库这个领域事件以及发票总库的业务逻辑,在发票领域服务内对外提供了发票入库、发票排重等方法,对于数据存储,则需要统一抽象了仓储接口(不受限于具体的数据存储介质)。
在战术设计阶段,我们完成了分层设计、规范设计等事项,在分层设计中我们遵循了DDD的四层分层模型,如下所示:
用户接口层主要实现后端服务与前端调用入口的接口数据适配和转换;应用层主要用来协调领域层多个聚合之间的服务组合和编排,以及负责领域事件的订阅和发布等职责;领域层是领域模型的核心,实现领域模型的核心业务逻辑,其内部逻辑相对稳定,不会受外界变化(如:底层数据存储介质的变换、通讯方式的变化等)的影响,基础层主要用来提供通用的基础工具类或基础服务。
结合上述四层模型,在实际代码工程的构建过程中,我们也是参考了COLA架构进行构建,具体如下:
作为服务的启动模块(SpringBootApplication)以及用户接口层(Controller),定义了所有对外发布的接口以及DTO到领域层实体类的转换。
应用层模块,提供了发票领域服务的方法定义及实现,同时作为事件总线(EventBus)的接收层,提供了多个发票领域事件的Handler处理器,对领域事件做下一步处理,比如:发送MQ消息等。
领域层模块,定义了发票领域的实体、值对象、发票事件、发票领域服务以及仓储层接口,同时包含了创建领域实体的工厂类。在发票领域服务类中,通过发票实体以及发票规则实体的协作,完成发票入库逻辑,当发票入库事件发生后,会通过事件总线(EventBus)将该领域事件发布出去。而在发票的聚合根实体内,则通过事务控制了发票实体以及发票日志实体的数据存储。
基础设施层模块,提供了发票仓储接口、发票日志仓储接口的实现类以及从领域实体类到仓储层PO对象类的转换,同时提供了事件总线(EventBus)以及加解密等通用工具类。
客户端模块,提供了API定义以及DTO、VO等对象的定义。
为了防止开发过程中的随心所欲,开发规范的制定就显得特别重要,但也是最容易被无视的点,其结果就是架构的一致性被严重破坏,代码的可维护性将急剧下降,架构将形同虚设。
首先是分包设计,在领域驱动设计里,推荐使用了基于业务的分包,即通过软件所实现的业务功能进行模块化划分,而不是从技术的角度划分(比如首先划分出service和infrastruture等包)
类名应该是自明的,也就是看到类名就知道里面是干了什么事,这也就反向要求我们的类也必须是单一职责的。为了避免团队人员对dto、bo、vo、po等这些pojo对象的二义性理解,我们制定了类名规范。
方法名约定
经过上述过程,我们按照DDD的方式完成了购车发票总库的迁移工作,目前系统已经顺利上线,因为大家都是第一次做DDD的落地实践,在整个过程中会发现从理论到实践难免会出现一些理解的偏差和分歧,比如:
解:创建聚合根通过Factory完成;业务逻辑优先在聚合根边界内完成;聚合根中不合适放置的业务逻辑才考虑放到DomainService中。
解:DDD的概念提出之初,事务的层次体现在跨领域交付的应用服务层。在微服务的流行的现阶段,各领域之间通过接口交互,在实施过程中,我们要灵活根据业务逻辑和当前的架构来把事务粒度做到最小。
解:读操作便可以根据自身所需独立设计数据结构,而不用受写模型数据结构的牵制。因此,领域驱动设计通常适用于增删改的业务操作,但不适用于分析统计。在一个系统中,增删改的业务可以采用领域驱动的设计,但在非增删改的分析汇总场景中,则不必采用领域驱动的设计,直接 SQL 查询就好了,也就不必再遵循聚合的约束了。
解:领域实体里的方法映射的是现实世界的动作行为,改变的是状态、时间,而领域服务是实体的能力,领域服务在设计上应该是无状态的,它存在的意义就是协调领域对象共完成某个操作。业务逻辑应该放在领域实体里还是领域服务里应该根据这个原则来评估。
在实践过程中出现这些问题其实都很正常,每一个问题都不一定会有标准答案,比如上述没有提到的问题:一个领域对象究竟是定义为实体还是值对象等,即便是上述我们有答案的问题也仅仅是我们的一些见解,仁者见仁,智者见智,面对这些问题,我们一方面需要摆脱掉之前的思维定式,再次深入的理解理论定义,一方面也需要借鉴参考一些成熟代码案例,找到适合我们自身项目的结论。
按照DDD的模式进行项目的落地实施,相比与传统开发模式也会有一些质的提升,主要表现在以下几方面:
DDD首先强调的是确定领域边界,领域边界确定后所有设计均会在同一个领域范围内进行开展,清晰的领域边界对应了实际系统落地过程中合理的系统边界,这对于后续系统内高内聚功能的延展起到了积极的帮助作用。
在传统的开发模式中,不同环节的人员经常会出现对于同一对象的叫法不一致、定义不一致的现象,这在一定程度上也造成了大家对于系统理解的偏差,而在DDD模式中,提出了通用语言的概念,参与其中的所有成员在系统生命周期内均采用简单清晰明了的通用语言进行交流,通用语言成为团队内外部系统交流的统一语言,既减少了信息的失真, 又确保了大家在同一领域知识体系内交流的便利性、理解的一致性。
传统开发模式采用贫血模型,将属性与业务逻辑彻底分离,通过get / set方法改变对象属性,对象属性可以被随意的修改,在面对简单的业务场景,贫血模型还可以应对,但是在面对复杂的业务场景时,传统开发模式的贫血模型维护成本高的弊端就会被暴露。与贫血模型相对应的,DDD的开发模式强调采用充血模型,将属性和行为聚合到一个实体内,业务逻辑更加便于维护和管理,也更加符合面向对象的编程逻辑,这也是基于充血模型的 DDD 开发模式越来越被人提倡的原因。
DDD所涵盖的范围其实是比较广泛的,在具体的实践过程中是需要结合实际的项目场景和团队成员的实际情况等多方面因素,不断的摸索,经过长时间的学习和积累,逐步的在团队内部建立DDD的方法论以及对应的开发模式、技术架构。
以上就是我们的DDD实践总结,欢迎对此感兴趣的同学与我们交流,共同进步。
转载 https://mp.weixin.qq.com/s/8nSxM5lDOzexFBNErdbjWg
Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved