写点什么

DDD 领域驱动设计实战 - 分层架构及代码目录结构

  • 2022 年 5 月 12 日
  • 本文字数:4761 字

    阅读完需:约 16 分钟

  • 接入层做和业务规则无关的 bean validation 验证

  • 准单体系统下,按照连接方式分包


该层指的是服务端用于适配端侧的部分,而非端侧本身。因为该层本就依赖应用层,无人使用接口在这里做依赖倒置,所有又被称作主动适配。


[](()1.1 细分结构




  • assembler、dto 和 fa?ade


[](()facade

提供较粗粒度的调用接口,将用户请求委派给一个或多个应用服务进行处理。比如调用应用层创建用户的方法。

[](()dto

数据传输的载体,内部不存在任何业务逻辑,可以通过 DTO 把内部的领域对象与外界隔离。


比如接收请求传入的数据 CustomerDTO。


不同的对象在不同的层转换。用户接口层 DTO 和 DO 转换,应用层主要是 DO,调外部微服务的服务的时候应用层有 dto 和 do 的转换。领域层与基础层之间,在基础层有 DO 和 PO 的转换。


在接口层定义 DTO 对象。数据可能来源于多个 DO 对象。

[](()assembler

实现 DTO 与 DO 间的相互转换和数据交换。


一般 assembler 与 dto 一同出现。比如创建用户时,将 CustomerDTO 转换为 CustomerEntity。你可以在用户接口层创建 DTO 类和 assembler 类。在 assembler 类里完成映射。


[](()2.2 应用层



[](()特点

  • 关心处理完一个完整的业务

  • 该层只负责业务编排,对象转换,实际业务逻辑由领域层完成

  • 不关心【请求从何处来】,但是关心【谁来、做什么、有没有权限做】


即复制安全认证、权限校验


  • 集成不同的领域服务解决问题


应用层位于领域层之上,因为领域层包含多个聚合,所以它可协调多个聚合服务和领域对象完成服务编排和组合,协作完成业务。


  • 最终一致性(最终一致性对业务有侵入)事务放到这层

  • 对应到分布式系统中的中台等概念

  • 方法级别的功能权限控制放到这层

  • 只产应用异常,对应 HTTP 状态码 403、401

  • 准单体系统下,按照应用划分模块


主要包含应用服务,理论上不应有业务规则或逻辑,而主要是面向用例和流程相关的操作。


  • 应用层也是微服务间的交互通道,它可调用其它微服务,完成微服务间的服务组合和编排


开发设计时,不要将本该放在领域层的业务逻辑放到应用层。庞大的应用层会使领域模型失焦,时间一长,微服务就会退化为 MVC


应用服务是在应用层,负责


  • 服务的组合、编排、转发、转换和传递,处理业务用例的执行顺序以及结果的拼装,以粗粒度服务通过 API 网关发布到前端

  • 发送或订阅领域事件

[](()应用层代码目录结构

存放应用层服务组合和编排相关的代码。


应用服务向下基于


  • 微服务内的领域服务,或

  • 外部微服务的应用服务


完成服务的编排和组合


向上为用户接口层提供各种应用数据展现支持服务。


应用服务和事件等代码会放在这层目录。


[](()Event(事件)

主要存放事件相关代码。包括两子目录:

[](()publish

主要存放事件发布相关代码。比如发布用户创建事件给其它微服务。

[](()subscribe

主要存放事件订阅相关代码(事件处理相关的核心业务逻辑在领域层实现)。


虽然应用层和领域层都可进行事件的发布和处理,但为实现事件的统一管理,推荐将微服务内所有事件的发布和订阅处理都统一放到应用层,事件相关的核心业务逻辑实现放在领域层。通过应用层调用领域层服务,来实现完整的事件发布和订阅处理流程。

[](()Service(应用服务)

应用服务会对多个领域服务或外部应用服务进行封装、编排和组合,对外提供粗粒度的服务。应用服务主要实现服务组合和编排,是一段独立的业务逻辑。


可以将所有应用服务放在一个应用服务类里,也可以把一个应用服务设计为一个应用服务类,以防应用服务类代码量过大。


比如内部服务->创建用户;外部服务->创建日志。


[](()2.3 领域层




主要包含聚合根、实体、值对象、领域服务等领域模型中的领域对象。


实现核心业务逻辑,通过各种校验保证业务正确性。领域层主要体现领域模型的业务能力,它用来表达业务概念、业务状态和业务规则。


领域模型的业务逻辑主要由实体和领域服务实现:


  • 实体采用充血模型 实现所有与之相关的业务功能。


实体和领域服务在实现业务逻辑上不是同级,当领域中的某些功能,单一实体或值对象无法实现,就会用到领域服务,它可组合聚合内的多个实体或值对象,实现复杂业务逻辑。


[](()3 Domain(领域层)


============================================================================


存放领域层核心业务逻辑相关的代码。


可包含多个聚合代码包,共同实现领域模型的核心业务逻辑。聚合以聚合内的实体、方法、领域服务和事件等代码会放在该层目录。


领域层包括一个或多个聚合的实体类、事件实体类、领域服务以及工厂、仓储相关代码。一个聚合对应一个聚合代码目录,聚合之间在代码上完全隔离,聚合之间通过应用层协调。


Domain 由一或多个聚合包构成,共同实现领域模型的核心业务逻辑。


聚合内的代码模型是标准和统一的,包括:entity、event、repository、service 子目录



[](()Aggregate(聚合)




聚合软件包的根目录,可根据实际项目的聚合名称命名,比如权限聚合。在聚合内定义聚合根、实体和值对象以及领域服务之间的关系和边界。聚合内实现高内聚的业务逻辑,它的代码可以独立拆分为微服务。


以聚合为单位的代码放在一个包里的主要是为业务内聚,更是为以后微服务之间聚合的重组。聚合之间清晰的代码边界,可让你轻松地实现以聚合为单位的微服务重组。

[](()实例

比如进入用户聚合目录下(如 CustomerAggregate)。


假设这样一个场景,主播账户作为一个聚合,优惠券模块作为一个聚合。那主播选券的命令属于主播账户聚合。然后主播账户里的优惠券就是这个聚合里的值对象。


如果有多个聚合, 比如聚合根 A 和聚合根 B, 从业务的角度讲,可以接受 AB 间数据的最终一致性,但从数据展示的角度考虑, A 和 B 是有强关联性的,也就是说在页面上,他们总是一起在页面的某部分出现, 那可以分别调两个聚合的领域服务,然后将两个聚合根的 DO 对象转换为一个 DTO,就可以给前端提供包含两个聚合数据的数据服务了。

[](()细分结构

[](()Entity(实体)

存放聚合根、实体、值对象以及工厂模式(Factory,工厂模式主要是实现复杂聚合的实体的数据初始化。如果实体太多,聚合根处理起来会很复杂,通过工厂一次初始化)相关代码。实体类采用充血模型,同一实体相关的业务逻辑都在实体类代码中实现。跨实体的业务逻辑代码在领域服务中实现。比如用户聚合根。

[](()Event(事件)

存放事件实体以及与事件活动相关的业务逻辑代码。比如创建用户的事件。

[](()Service(领域服务)

存放领域服务代码。一个领域服务是多个实体组合出来的一段业务逻辑。你可以将聚合内所有领域服务都放在一个领域服务类中,你也可以把每一个领域服务设计为一个类。如果领域服务内的业务逻辑相对复杂,我建议你将一个领域服务设计为一个领域服务类,避免由于所有领域服务代码都放在一个领域服务类中,而出现代码臃肿的问题。领域服务封装多个实体或方法后向上层提供应用服务调用。比如具体的创建用户逻辑,比如用户是否重复校验,分配初始密码等。

[](()Repository(仓储)

存放所在聚合的查询或持久化领域对象的代码,通常包括仓储接口和仓储实现方法。为了方便聚合的拆分和组合,设定原则:一个聚合对应一个仓储。比如将用户信息保存到数据库。


按 DDD 分层架构,仓储实现本应属基础层代码,但为在微服务架构演进时,保证代码拆分和重组的便利性,把聚合仓储实现的代码放到聚合包内。这样,如果需求或设计发生变化导致聚合需要拆分或重组,就可将包括核心业务逻辑和仓储代码的聚合包整体迁移,轻松实现微服务架构演进。


[](()2.4 基础层




为其它各层提供通用技术基础服务:


  • 三方工具

  • 驱动

  • MQ

  • API 网关

  • 文件

  • 缓存

  • DB


最常用的


基础层包含基础服务,它采用依赖反转,封装基础资源服务,实现应用层、领域层与基础层解耦。


MVC 架构由于上层应用对 DB 强耦合,很多公司在架构演进最怕换 DB,一旦更换,可能需重写一堆代码。


但采用依赖反转,应用层即可通过解耦保持独立核心业务逻辑。当 DB 变更,只需更换 DB 基础服务。


[](()4 Infrastructur 《一线大厂 Java 面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》无偿开源 威信搜索公众号【编程进阶路】 e(基础层)


====================================================================================


主要存放基础资源服务相关的代码,为其它各层提供的通用技术能力、三方软件包、数据库服务、配置和基础资源服务的代码都会放在这一层目录里。


Infrastructure 的代码目录结构有:config 和 util 两个子目录。



  • Config


主要存放配置相关代码。


  • Util


存放平台、开发框架、消息、数据库、缓存、文件、总线、网关、第三方类库、通用算法等基础代码,你可以为不同的资源类别建立不同的子目录。


[](()3 微服务架构演进


========================================================================


领域模型中对象的层次从内到外依次是:值对象、实体、聚合和限界上下文。


实体或值对象的简单变更,一般不会让领域模型和微服务发生大变。但聚合的重组或拆分却可以。因为聚合内业务功能内聚,能独立完成特定业务。那聚合的重组或拆分,势必引起业务模块和系统功能变化。


可以聚合为基础单元,完成领域模型和微服务架构的演进。


聚合可作为整体,在不同领域模型间重组或拆分,或直接将一个聚合独立为微服务。


[](()微服务架构的演进案例




现有


微服务 1:包含聚合 a、b、c


微服务 2:


微服务 3:包含聚合 d、e、f


  • 当发现微服务 1 中聚合 a 的功能经常被高频访问,以致拖累了整个微服务 1 的性能,可把聚合 a,从微服务 1 中剥离,独立为微服务 2 以应对高性能场景

  • 随业务发展,发现微服务 3 的领域模型变化,聚合 d 会更适合放到微服务 1 的领域模型。即可将聚合 d 整体迁移到微服务 1。注意定义好聚合间的代码边界

  • 架构演进后,微服务 1 从最初包含聚合 a、b、c,演进为包含聚合 b、c、d 的新领域模型和微服务


可见,好的聚合和代码模型的边界设计,可让你快速应对业务变化,轻松实现领域模型和微服务架构演进。


[](()微服务内服务的演进


========================================================================


在微服务内部,实体的方法被领域服务组合和封装,领域服务又被应用服务组合和封装。在服务逐层组合和封装的过程中,你会发现这样一个有趣的现象。



服务设计时,你并不一定能完整预测有哪些下层服务会被多少个上层服务组装,因此领域层通常只提供一些原子服务,比如领域服务 a、b、c。但随系统功能增强和外部接入越来越多,应用服务不断丰富。终有一日,你会发现领域服务 b 和 c 同时多次被多个应用服务调用了,执行顺序也基本一致。这时你可以考虑将 b 和 c 合并,再将应用服务中 b、c 的功能下沉到领域层,演进为新的领域服务(b+c)。这样既减少了服务的数量,也减轻了上层服务组合和编排的复杂度。


这就是服务演进,领域模型会越来越能适应需求快速变化。


[](()从 MVC 跨越到 DDD


=========================================================================


由于层间松耦合,可专注本层设计,而不必关心其它层,也不必担心自己的设计会影响其它层。即 DDD 成功降低层与层之间的依赖。


分层架构使得程序结构更清晰,升级和维护更容易。修改某层代码时,只要本层接口参数不变,其它层不必修改。即使本层接口发生变化,也只影响相邻的上层,修改工作量小且可控。


传统企业应用大多是单体架构,而单体架构则大多是三层架构。三层架构解决了程序内代码间调用复杂、代码职责不清的问题,但这种分层是逻辑概念,在物理上它是中心化的集中式架构,并不适合分布式微服务架构。


DDD 分层要类似三层架构,只是在 DDD 中,这些要素被重新划分了层,确定了层与层之间的交互规则和职责边界。



DDD 分层架构相比 MVC(只有 API)在用户接口层新增了 DTO,给前端提供了更多的可使用数据和更高的展示灵活性。


DDD 分层架构对三层架构的业务逻辑层进行了更清晰的划分,改善了三层架构核心业务逻辑混乱,代码改动相互影响大的情况。


MVC 架构向 DDD 分层架构演进,主要发生在业务逻辑层和数据访问层。


DDD 分层架构将业务逻辑层的服务拆分到了应用层和领域层:


  • 应用层快速响应前端的变化

  • 领域层实现领域模型的能力

用户头像

还未添加个人签名 2022.04.13 加入

还未添加个人简介

评论

发布
暂无评论
DDD领域驱动设计实战-分层架构及代码目录结构_Java_爱好编程进阶_InfoQ写作社区