DDD 领域驱动设计实战 - 分层架构及代码目录结构
[](()1.1 分层架构的基本原则
每层只与位于其下方的层发生耦合。
[](()1.2 分层架构的分类
严格分层架构(Strict Layers Architecture)
某层只能与其直接下层耦合,即我的奴隶的奴隶,不是我的奴隶。
松散分层架构(Relaxed Layers Architecture)
允许任意上层与任意下层耦合。由于用户接口层和应用服务通常需要与基础设施打交道,许多系统都是该架构。
较低层有时也可与较高层耦合,但只限于采用观察者 (Observer)模式或者调停者(Mediator)模式场景。
较低层绝不能直接访问较高层。例如,在使用调停者模式时,较高层可能实现了较低 Java 开源项目【ali1024.coding.net/public/P7/Java/git】 层的接口,然后将实现对象作为参数传递到较低层。当较低层调用该实现时, 它并不知道实现出自何处。
[](()1.3 分层架构演进
[](()1.3.1 传统四层架构
将领域模型和业务逻辑分离出来,并减少对基础设施、用户界面甚至应用层逻辑的依赖,因为它们不属业务逻辑。将一个夏杂的系统分为不同的层,每层都应该具有良好的内聚性,并且只依赖于比其自身更低的层。
传统分层架构的基础设施层位于底层,持久化和消息机制便位于该层。
这里的消息包含
MQ 消息
SMTP
文本消息(SMS)
可将基础设施层中所有组件看作应用程序的低层服务,较高层与该层发生耦合以复用技术基础设施。即便如此,依然应避免核心的领域模型对象与基础设施层直接耦合
。
[](()1.3.2 改良版四层架构
[](()传统架构的缺陷
DDD 初创开发团队发现,将基础设施层放在最底层存在缺点,比如此时领域层中的一些技术实现就很困难:
违背分层架构的基本原则
难以编写测试用例
何解?
使用依赖反转设计原则:低层服务(如基础设施层)应依赖高层组件(比如用户界面层、应用层和领域层)所提供的接口。
[](()应用依赖反转原则
依赖反转原则后的分层方式:基础设施层在最上方,可实现所有其他层中定义的接口
依赖反转原则真的可以支持所有层吗? 《一线大厂 Java 面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》开源
有人认为依赖反转原则中只存在两层:最上方和最下方,上层实现下层定义的抽象接口。因此上图的基础设施层将位于最上方,而用户接口层、应用层和领域层应作同层且都位于下方。对此大家可保留自己意见。
[](()2 各层职责
=====================================================================
[](()2.1 Interfaces(用户接口层)
一般包括用户接口、Web 服务等。
只处理用户显示和用户请求,不应包含领域或业务逻辑。
有人认为,既然用户接口需验证用户输入,就无可避免应该包含业务逻辑。事实上,用户接口所进行的验证和对领域模型的验证不同:对那些粗制滥造且只面向领域模型的验证行为,应该予以限制。
如果用户接口使用了领域模型中的对象,那么此时领域对象仅限于数据渲染展现。在采用这种方式时,可使用展现模型对用户接口与领域对象进行解耦。
由于用户可能是人,也可能是其他系统,有时用户接口层将采用开放主机服务的方式向外提供 API。
用户接口层是应用层的直接用户。
用户接口层在于前后端调用的适配。若你的微服务要提供服务给很多外部应用,而对每个外部应用的入参出参都不同,你不可能开发一堆一对一的应用服务,这时 Facade 接口就起到了很好的作用,包括 DO 和 DTO 对象的组装和转换。
由于主要负责接入各种终端,所以该层有人也叫接入层。
实际开发中,我们都会感受到该层依附于应用层存在。随前后端分离,Restful API 流行,对简单的系统来说该层越来越弱化。对于有终端接入的系统来说,该层并不简单,需要处理各种协议适配:XMPP、websocket、MQTT 等。
复杂度不高时,我们往往把该层和应用层合并部署,主要凭开发经验和理解程度来决定。
存放用户接口层与前端交互、展现数据相关的代码。
前端应用通过这层接口,向应用服务获取展现所需的数据。
该层主要用来处理用户发送的 Restful 请求,解析用户输入的配置文件,并将数据传递给应用层。
数据的组装、数据传输格式以及 Facade 接口等代码都会放在这一层目录里。
封装应用服务和对外暴露接口。
[](()特点
关心视图和对外的服务,Restful、页面渲染、websocket、XMPP 连接等
如果没有多种用户端接入方式,可以和应用层合并
对应到分布式系统中的网关、BFF、前台等概念
只产生接入异常,例如数据校验,对应 HTTP 状态码 400、415 等
一个应用可以有多个接入层
接入层做和业务规则无关的 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(领域层)
最后
每年转战互联网行业的人很多,说白了也是冲着高薪去的,不管你是即将步入这个行业还是想转行,学习是必不可少的。作为一个 Java 开发,学习成了日常生活的一部分,不学习你就会被这个行业淘汰,这也是这个行业残酷的现实。
如果你对 Java 感兴趣,想要转行改变自己,那就要趁着机遇行动起来。或许,这份限量版的 Java 零基础宝典能够对你有所帮助。
评论