DDD 领域驱动设计实战(分层架构)

????????关注后回复 “进群” ,拉你进程序员交流群????????

作者丨JavaEdge在掘金

来源:

https://juejin.cn/post/6920458240165675022

  • 1 DDD分层架构

    • 1.1 分层架构的基本原则

    • 1.2 分层架构的分类

    • 1.3 分层架构演进

  • 2 各层职责

    • 2.1 用户接口层

    • 2.2 应用层

    • 2.3 领域层

    • 2.4 基础层

  • 3 微服务架构演进

    • 微服务架构的演进案例

    • 微服务内服务的演进

    • 三层架构如何演进到DDD分层架构?

  • 总结


整洁架构、CQRS、六边形架构等微服务架构都旨在“高内聚低耦合”。那DDD分层架构又如何?

1 DDD分层架构

1.1 分层架构的基本原则

每层只能与位于其下方的层发生耦合。

1.2 分层架构的分类

  • 严格分层架构(Strict Layers Architecture)

某层只能与其直接下层耦合,即我的奴隶的奴隶,不是我的奴隶。

  • 松散分层架构(Relaxed Layers Architecture)

允许任意上层与任意下层耦合。由于用户接口层和应用服务通常需要与基础设施打交道,许多系统都是该架构。

较低层有时也可与较高层耦合,但只限于采用观察者 (Observer)模式或者调停者(Mediator)模式场景。较低层 绝不能直接访问 较高层。例如,在使用调停者模式时,较高层可能实现了较低层的接口,然后将实现对象作为参数传递到较低层。当较低层调用该实现时, 它并不知道实现出自何处。

1.3 分层架构演进

1.3.1 传统四层架构
图片

将领域模型和业务逻辑分离出来,并减少对基础设施、用户界面甚至应用层逻辑的依赖,因为它们不属业务逻辑。将一个夏杂的系统分为不同的层,每层都应该具有良好的内聚性,并且只依赖于比其自身更低的层。

传统分层架构的 基础设施层 位于底层,持久化和消息机制便位于该层。这里的消息包含MQ消息、SMTP或文本消息(SMS)。可将基础设施层中所有组件看作应用程序的低层服务,较高层与该层发生耦合以复用技术基础设施。即便如此,依然应避免核心的领域模型对象与基础设施层直接耦合。

1.3.2 改良版四层架构
传统架构的缺陷

DDD初创开发团队发现,将基础设施层放在最底层存在缺点。比如此时领域层中的一些技术实现令人头疼:

  • 违背分层架构的基本原则

  • 难以编写测试用例

何解?

使用 依赖反转设计原则 :低层服务(如基础设施层)应依赖高层组件(比如用户界面层、应用层和领域层)所提供接口。

应用依赖反转原则
  • 依赖反转原则后的分层方式:基础设施层在最上方,可实现所有其他层中定义的接口

图片
依赖反转原则真的可以支持所有层吗?

有人认为依赖反转原则中只存在两层:最上方和最下方,上层实现下层定义的抽象接口。因此上图的基础设施层将位于最上方,而用户接口层、应用层和领域层应作 同层且都位于下方 。对此大家可保留自己意见。在六边形[Cockburn]或端口和适配器架构中对此做详细讲解。

2 各层职责

2.1 用户接口层

一般包括用户接口、Web 服务等。

只处理用户显示和用户请求,不应包含领域或业务逻辑。有人可能认为,既然用户接口需验证用户输入,那就应该包含业务逻辑。事实上,用户接口所进行的验证和对领域模型的验证不同:对那些粗制滥造且只面向领域模型的验证行为,应该予以限制。

如果用户接口使用了领域模型中的对象,那么此时领域对象仅限于数据渲染展现。在采用这种方式时,可使用展现模型对用户接口与领域对象进行解耦。由于用户可能是人,也可能是其他系统,有时用户接口层将采用开放主机服务的方式向外提供API。用户接口层是应用层的直接用户。

用户接口层很重要,在于前后端调用的适配。若你的微服务要面向很多应用或渠道提供服务,而每个渠道的入参出参都不一样,你不太可能开发出太多应用服务,这样Facade接口就起很好的作用了,包括DO和DTO对象的组装和转换等。

2.2 应用层

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

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

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

开发设计时,不要将本该放在领域层的业务逻辑放到应用层。因为庞大的应用层会使领域模型失焦,时间一长微服务就会演化为传统MVC三层架构,导致业务逻辑混乱。

应用服务是在应用层,负责服务的组合、编排、转发、转换和传递,处理业务用例的执行顺序以及结果的拼装,以粗粒度服务通过API网关发布到前端。还可进行安全认证、权限校验、事务控制、发送或订阅领域事件等。

2.3 领域层

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

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

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

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

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

2.4 基础层

为其它各层提供通用的技术基础服务,包含三方工具、驱动、MQ、API网关、文件、缓存、DB、基础服务等。最常用的还是提供DB持久化。

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

传统架构由于上层应用对DB强耦合,很多公司在架构演进最怕换DB,一旦更换,可能需重写一堆代码。但采用依赖反转,应用层即可通过解耦保持独立核心业务逻辑。当DB变更,只需更换DB基础服务。

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)。这样既减少了服务的数量,也减轻了上层服务组合和编排的复杂度。

你看,这就是服务演进的过程,它是随着你的系统发展的,最后你会发现你的领域模型会越来越精炼,越来越能适应需求的快速变化。

三层架构如何演进到DDD分层架构?

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

其次,分层架构使得程序结构变得清晰,升级和维护更加容易。我们修改某层代码时,只要本层的接口参数不变,其它层可以不必修改。即使本层的接口发生变化,也只影响相邻的上层,修改工作量小且错误可以控制,不会带来意外的风险。

那我们该怎样转向DDD分层架构呢?不妨看看下面这个过程。

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

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

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

DDD分层架构在用户接口层引入了DTO,给前端提供了更多的可使用数据和更高的展示灵活性。

DDD分层架构对三层架构的业务逻辑层进行了更清晰的划分,改善了三层架构核心业务逻辑混乱,代码改动相互影响大的情况。DDD分层架构将业务逻辑层的服务拆分到了应用层和领域层。应用层快速响应前端的变化,领域层实现领域模型的能力。

另外一个重要的变化发生在数据访问层和基础层之间。三层架构数据访问采用DAO方式;DDD分层架构的数据库等基础资源访问,采用了仓储(Repository)设计模式,通过依赖倒置实现各层对基础资源的解耦。

关于仓储。仓储本身属于基础层,但考虑到一个聚合对应一个仓储,为了以后聚合代码整体迁移方便,在微服务代码目录设计时,在聚合目录下增加一个Repository的仓储目录,跟仓储相关的代码都在这个目录下。

这个目录下的代码与聚合的其它业务代码是分开的。如果未来换数据库,只需将Repository目录下的代码替换。而如果聚合需要整体迁移到其它微服务中去,仓储的代码也会一并迁移。

仓储又分为两部分:仓储接口和仓储实现。仓储接口放在领域层中,仓储实现放在基础层。原来三层架构通用的第三方工具包、驱动、Common、Utility、Config等通用的公共的资源类统一放到了基础层。

总结

DDD分层架构包含用户接口层、应用层、领域层和基础层。通过这些层次划分,我们可以明确微服务各层的职能,划定各领域对象的边界,确定各领域对象的协作方式。

-End-

最近有一些小伙伴,让我帮忙找一些 面试题 资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是程序员面试必备!所有资料都整理到网盘了,欢迎下载!

点击????卡片,关注后回复【面试题】即可获取

在看点这里好文分享给更多人↓↓

第一部分 背景知识 第1章 应重视的价值,也是对过去几年的沉重反思 1.1 总体价值 1.2 应重视的架构风格 1.2.1 焦点之一:模型 1.2.2 焦点之二:用例 1.2.3 如果重视模型,就可以使用领域模型模式 1.2.4 慎重处理数据库 1.2.5 领域模型与关系数据库之间的阻抗失配 1.2.6 谨慎处理分布式 1.2.7 消息传递很重要 1.3 对过程的各个组成部分的评价 1.3.1 预先架构设计 1.3.2 领域驱动设计 1.3.3 测试驱动开发 1.3.4 重构 1.3.5 选择一种还是选择组合 1.4 持续集成 1.4.1 解决方案(或至少是正确方向上的一大步) 1.4.2 从我的组织汲取的教训 1.4.3 更多信息 1.5 不要忘记运行机制 1.5.1 有关何时需要运行机制的一个例子 1.5.2 运行机制的一些例子 1.5.3 它不仅仅是我们的过错 1.6 小结 第2章 模式起步 2.1 模式概述 2.1.1 为什么要学习模式 2.1.2 在模式方面要注意哪些事情 2.2 设计模式 2.3 架构模式 2.3.1 示例:层 2.3.2 另一个示例:领域模型模式 2.4 针对具体应用程序类型的设计模式 2.5 领域模式 2.6 小结 第3章 TDD与重构 3.1 TDD 3.1.1 TDD流程 3.1.2 演示 3.1.3 设计效果 3.1.4 问题 3.1.5 下一个阶段 3.2 模拟和桩 3.2.1 典型单元测试 3.2.2 声明独立性 3.2.3 处理困难因素 3.2.4 用测试桩替换协作对象 3.2.5 用模拟对象替换协作对象 3.2.6 设计含义 3.2.7 结论 3.2.8 更多信息 3.3 重构 3.4 小结 第二部分 应用DDD 第4章 新的默认架构 4.1 新的默认架构的基础知识 4.1.1 从以数据库为中心过渡到以领域模型为中心 4.1.2 进一步关注DDD 4.1.3 根据DDD进行分层 4.2 轮廓 4.2.1 领域模型示例的问题/特性 4.2.2 逐个处理特性 4.2.3 到目前为止的领域模型 4.3 初次尝试将UI与领域模型挂接 4.3.1 基本目标 4.3.2 简单UI的当前焦点 4.3.3 为客户列出订单 4.3.4 添加订单 4.3.5 刚才我们看到了什么 4.4 另一个维度 4.4.1 领域模型的位置 4.4.2 孤立或共享的实例 4.4.3 有状态或无状态领域模型实例化 4.4.4 领域模型的完整实例化或子集实例化 4.5 小结 第5章 领域驱动设计进阶 5.1 通过简单的TDD实验来精化领域模型 5.1.1 从Order和OrderFactory的创建开始 5.1.2 一些领域逻辑 5.1.3 第二个任务:OrderRepository+OrderNumber 5.1.4 重建持久化的实体:如何从外部设置值 5.1.5 获取订单列表 5.1.6 该到讨论实体的时候了 5.1.7 再次回到流程上来 5.1.8 总览图 5.1.9 建立OrderRepository的伪实现 5.1.10 简单讨论一下保存 5.1.11 每个订单的总量 5.1.12 历史客户信息 5.1.13 实例的生命周期 5.1.14 订单类型 5.1.15 订单的介绍人 5.2 连贯接口 5.3 小结 第6章 准备基础架构 6.1 将POCO作为工作方式 6.1.1 实体和值对象的PI 6.1.2 是否使用PI 6.1.3 运行时与编译时PI 6.1.4 PI实体/值对象的代价 6.1.5 将PI用于存储库 6.1.6 单组存储库的代价 6.2 对保存场景的处理 6.3 建立伪版本机制 6.3.1 伪版本机制的更多特性 6.3.2 伪版本的实现 6.3.3 影响单元测试 6.4 数据库测试 6.4.1 在每次测试之前重置数据库 6.4.2 在测试运行期间保持数据库的状态 6.4.3 测试之前重置测试所使用的数据 6.4.4 不要忘记不断演变的模式 6.4.5 分离单元测试和数据库调用测试 6.5 查询 6.5.1 单组查询对象 6.5.2 单组查询对象的代价 6.5.3 将查询定位到哪里 6.5.4 再次将聚合作为工具 6.5.5 将规格用于查询 6.5.6 其他查询选择 6.6 小结 第7章 应用规则 7.1 规则的分类 7.2 规则的原则及用法 7.2.1 双向规则检查:可选的(可能的)主动检查,必需的(和自动的)被动检查 7.2.2 所有状态(即使是错误状态)都应该是可保存的 7.2.3 规则应该高效使用 7.2.4 规则应该是可配置的,以便添加自定义规则 7.2.5 规则应与状态放在一起 7.2.6 规则应该具有很高的可测试性 7.2.7 系统应阻止我们进入错的状态 7.3 开始创建API 7.3.1 上下文,上下文,还是上下文 7.3.2 数据库约束 7.3.3 将规则绑定到与领域有关的转换,还是绑定到与基础架构有关的转换 7.3.4 精化原则:所有状态,即使是错误状态,都应该是可保存的 7.4 与持久化有关的基本的规则API的需求 7.4.1 回到已发现的API问题上 7.4.2 问题是什么 7.4.3 我们允许了不正确的转换 7.4.4 如果忘记检查怎么办 7.5 关注与领域有关的规则 7.5.1 需要合作的规则 7.5.2 使用基于集合的处理方法 7.5.3 基于服务的验证 7.5.4 在不应该转换时尝试转换 7.5.5 业务ID 7.5.6 避免问题 7.5.7 再次将聚合作为工具 7.6 扩展API 7.6.1 查询用于设置UI的规则 7.6.2 使注入规则成为可能 7.7 对实现进行精化 7.7.1 一个初步实现 7.7.2 创建规则类,离开最不成熟的阶段 7.7.3 设置规则列表 7.7.4 使用规则列表 7.7.5 处理子列表 7.7.6 一个API改进 7.7.7 自定义 7.7.8 为使用者提供元数据 7.7.9 是否适合用模式来解决此问题 7.7.10 复杂规则又是什么情况 7.8 绑定到持久化抽象 7.8.1 使验证接口成为可插入的 7.8.2 在保存方面实现被动验证的替代解决方案 7.8.3 重用映射元数据 7.9 使用泛型和匿名方法 7.10 其他人都做了什么 7.11 小结 第三部分 应用PoEAA 第8章 用于持久化的基础架构 8.1 持久化基础架构的需求 8.2 将数据存储到哪里 8.2.1 RAM 8.2.2 文件系统 8.2.3 对象数据库 8.2.4 关系数据库 8.2.5 使用一个还是多个资源管理器 8.2.6 其他因素 8.2.7 选择和前进 8.3 方法 8.3.1 自定义手工编码 8.3.2 自定义代码的代码生成 8.3.3 元数据映射(对象关系(O/R)映射工具) 8.3.4 再次选择 8.4 分类 8.4.1 领域模型风格 8.4.2 映射工具风格 8.4.3 起点 8.4.4 API焦点 8.4.5 查询风格 8.4.6 高级数据库支持 8.4.7 其他功能 8.5 另一个分类:基础架构模式 8.5.1 元数据映射:元数据的类型 8.5.2 标识字段 8.5.3 外键映射 8.5.4 嵌入值 8.5.5 继承解决方案 8.5.6 标识映射 8.5.7 操作单元 8.5.8 延迟加载/立即加载 8.5.9 并发控制 8.6 小结 第9章 应用NHibernate 9.1 为什么使用NHibernate 9.2 NHibernate简介 9.2.1 准备 9.2.2 一些映射元数据 9.2.3 一个小的API示例 9.2.4 事务 9.3 持久化基础架构的需求 9.3.1 高级持久化透明 9.3.2 持久化实体的生命周期所需的特定特性 9.3.3 谨慎处理关系数据库 9.4 分类 9.4.1 领域模型风格 9.4.2 映射工具风格 9.4.3 起点 9.4.4 API焦点 9.4.5 查询语言风格 9.4.6 高级数据库支持 9.4.7 其他功能 9.5 另一种分类:基础架构模式 9.5.1 元数据映射:元数据类型 9.5.2 标识字段 9.5.3 外键映射 9.5.4 嵌入值 9.5.5 继承解决方案 9.5.6 标识映射 9.5.7 操作单元 9.5.8 延迟加载/立即加载 9.5.9 并发性控制 9.5.10 额外功能:验证挂钩 9.6 NHibernate和DDD 9.6.1 程序集概览 9.6.2 ISession和存储库 9.6.3 ISession、存储库和事务 9.6.4 得到了什么结果 9.7 小结 第四部分 下一步骤 第10章 博采其他设计技术 10.1 上下文为王 10.1.1 层和分区 10.1.2 分区的原因 10.1.3 限界上下文 10.1.4 限界上下文与分区有何关联 10.1.5 向上扩展DDD项目 10.1.6 为什么对领域模型——SO分区 10.2 SOA简介 10.2.1 什么是SOA 10.2.2 为什么需要SOA 10.2.3 SOA有什么不同 10.2.4 什么是服务 10.2.5 服务中包括什么 10.2.6 深入分析4条原则 10.2.7 再来看一下什么是服务 10.2.8 OO在SOA中的定位 10.2.9 客户-服务器和SOA 10.2.10 单向异步消息传递 10.2.11 SOA如何提高可伸缩性 10.2.12 SOA服务的设计 10.2.13 服务之间如何交互 10.2.14 SOA和不可用的服务 10.2.15 复杂的消息传递处理 10.2.16 服务的可伸缩性 10.2.17 小结 10.3 控制反转和依赖注入 10.3.1 任何对象都不是孤岛 10.3.2 工厂、注册类和服务定位器 10.3.3 构造方法依赖注入 10.3.4 setter依赖注入 10.3.5 控制反转 10.3.6 使用了Spring.NET框架的依赖注入 10.3.7 利用PicoContainer.NET进行自动装配 10.3.8 嵌套容器 10.3.9 服务定位器与依赖注入的比较 10.3.10 小结 10.4 面向方面编程 10.4.1 热门话题有哪些 10.4.2 AOP术语定义 10.4.3 .NET中的AOP 10.4.4 小结 10.5 小结 第11章 关注UI 11.1 提前结语 11.2 模型-视图-控制器模式 11.2.1 示例:Joe的Shoe Shop程序 11.2.2 通过适配器简化视图界面 11.2.3 将控制器从视图解耦 11.2.4 将视图和控制器结合起来 11.2.5 是否值得使用MVC 11.3 测试驱动的Web窗体 11.3.1 背景 11.3.2 一个示例 11.3.3 领域模型 11.3.4 GUI的TDD 11.3.5 Web窗体实现 11.3.6 小结 11.3.7 用NMock创建模拟 11.4 映射和包装 11.4.1 映射和包装 11.4.2 用表示模型来包装领域模型 11.4.3 将表示模型映射到领域模型 11.4.4 管理关系 11.4.5 状态问题 11.4.6 最后的想法 11.5 小结 11.6 结束语 第五部分 附录 附录A 其他领域模型风格 附录B 已讨论的模式的目录
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值