三层架构的问题

在前文中,我从基础代码的角度探讨了如何运用领域驱动设计(DDD)来实现高内聚低耦合的代码。本篇文章将从项目架构的角度,继续探讨三层架构与DDD之间的演化过程,以及DDD如何优化架构的问题。

三层架构作为一种常见的软件架构模式,将应用程序分为展示层、业务逻辑层和数据访问层,具有以下优点:

  1. 分离关注点: 三层架构将不同功能模块分隔开,使每个模块专注于特定任务,降低了代码复杂性。
  2. 可维护性和可扩展性: 不同层之间的松耦合使得对某一层的修改不会影响其他层,有助于系统的维护和扩展。
  3. 可测试性: 不同层的独立性使得单元测试和集成测试更容易实现,有助于确保代码质量。

然而,尽管三层架构有其优点,在处理复杂业务时,三层架构也可能面临一些问题。具体有:

  1. 业务逻辑分散: 在三层架构中,业务逻辑往往分散在不同的层中,导致业务流程难以理清,影响了代码的可读性和可维护性。
  2. 领域模型贫血: 三层架构中,领域逻辑和数据存储混合在一起,导致领域模型的业务方法受限,难以表达复杂的业务规则。
  3. 过度依赖数据存储: 不同层之间对数据存储的依赖紧密,当切换数据存储介质时,需要大量修改代码。

具体具体示意如下图:

随着业务的不断复杂化,service层变得越来越庞大,服务之间的引用也变得越来越混乱,这为项目带来了风险和不确定性。

三层架构演化到DDD

在三层架构的演化过程中,有时会尝试引入额外的"Manager"层来管理服务层的功能,但这并不是DDD所倡导的概念。在DDD中,更加关注领域的划分和内聚,以及如何将领域模型与业务需求对应起来。

一般情况下,三层架构的问题可以通过引入领域驱动设计来解决。在以下内容中,我们将重点放在如何将DDD思想融入现有的三层架构中,以实现更高内聚、更低耦合的代码架构。

  1. 领域的划分: DDD将service层按业务场景划分成不同的领域,每个领域内包含实体、值对象、聚合根等元素。
  2. 内聚的领域: 在领域内,业务尽量内聚,避免领域之间的耦合。每个领域内部可以根据需要建立更细粒度的子域,进一步提高内聚性。
  3. 应用层的组合: 引入一个Application层,将领域内的service组合调用,形成业务服务,避免服务之间直接引用,降低耦合度。

经过我们的修改,三层架构可以(组合和聚合)演进到右侧架构模式,通过这种方式,我们能够更好地组织和管理代码,实现领域内高内聚低耦合的目标。

代码组织

在进行了基础代码的优化后,接下来我们将探讨如何根据领域驱动设计(DDD)思想来优化整体代码架构。经过前面的分析,我们大致了解了DDD的项目结构,并且明确了每个层次的职责。现在,让我们更详细地探讨每个层次的代码组织。

  1. Domain层: 该层是DDD的核心,包含了领域对象、值对象、聚合根等,以及领域内的业务逻辑和规则。在领域内,业务逻辑应该尽量内聚,领域间应该尽量松耦合。
  2. 基础架构层: 包括仓储实现,缓存实现,队列实现等等系列系统需要的基础能力,这一层的目的是为整个项目提供基础支持。
  3. Application层: 这一层用于组合领域内的服务,形成具体的应用用例。它不包含具体的业务逻辑,只是通过调用领域内的服务来实现具体的功能。
  4. UI层: UI层负责展示数据和接收用户输入,它不包含业务逻辑,只是通过调用Application层来触发业务流程。

具体架构类似如下图:

当将领域驱动设计(DDD)引入到项目架构中,代码的组织方式会有所不同,以更好地体现领域的业务逻辑和关系。让我们详细解释每个层次的代码组织,为了保证阅读的连贯性,我们从引用的最低层(domain层)开始说起

Domain层:

Domain层是DDD的核心,它包含了领域对象、值对象、聚合根等,以及领域内的业务逻辑和规则。在这一层,你应该更关注领域的核心业务,让代码更贴近业务现实。以下是一些代码组织的思路:

  • 实体和值对象: 领域对象可以分为实体和值对象。实体是有唯一标识的对象,通常代表业务概念;值对象是没有唯一标识的对象,它们通常用来描述实体的属性。在这一层,你应该为每个实体和值对象定义其属性和行为。

  • 聚合和聚合根: 将相关联的实体和值对象组合成聚合,聚合根是聚合的入口。聚合根负责保持聚合内的一致性,它是领域模型的核心部分。

  • 领域服务: 领域服务用于处理一些领域范围内的业务逻辑,它们不属于任何具体的实体或值对象。将这些逻辑封装在领域服务中可以使领域模型更加清晰。

  • 通用工具类: 通用工具类是一些与领域相关的辅助方法,可以被领域内的多个实体或值对象使用。将通用工具类放在领域层可以更方便地供领域内的实体使用,避免在其他层重复实现。

在domain域内提供,entity(实体),valueobj(值对象),AggregateRoot(聚合根),仓储接口(IRepository),事件驱动相关(event)

基础架构层:

在基础架构层,我们主要关注与系统的基础设施和通用功能。这一层包含仓储模式和接口适配器,用于封装数据存储操作并为领域层提供统一的数据访问接口。通用工具类也可以在这里定义和实现,为领域层和应用层提供通用的辅助功能。基础架构层的代码组织通常如下:

  • 第三方库封装: 如果项目使用了第三方库或框架,你可以在基础架构层进行封装,以便在其他层中更方便地使用。封装可以包括对第三方库的初始化、配置以及封装特定的操作接口。

  • 仓储接口和适配器: 在基础架构层中定义仓储接口,以及不同数据存储介质的适配器实现。这样可以将数据访问操作与领域层解耦,同时实现数据存储的切换。

  • 中间件实现: 如果系统使用了中间件,如缓存、消息队列等,你可以在基础架构层实现中间件的具体操作。这有助于将与中间件相关的逻辑隔离在基础架构层中。

  • 事件驱动实现: 如果系统采用了事件驱动的架构,你可以在基础架构层实现事件的发布与订阅机制,以及事件的处理逻辑。

如上图,使用redis提供缓存,使用kafka提供消息队列,使用guava提供事件驱动,仓储层负责实现仓储功能

Application层:

Application层用于组合领域内的服务,形成具体的应用用例。它不包含具体的业务逻辑,而是通过调用领域内的服务来实现功能。在这一层,你可以有以下的组织方式:

  • 应用服务: 应用服务负责处理用户请求,协调领域内的服务,形成具体的用例。每个应用服务通常对应一个用户操作,它们应该是轻量级的,不涉及具体的业务逻辑。
  • DTO(数据传输对象): DTO负责承接前端传入的数据,为领域层转换为对应的业务参数。它们将用户输入的数据进行封装,以便传递给领域层进行处理。
  • 数据转换: 在应用层,你可能需要将领域对象转换为DTO,用于与UI层进行数据交互。数据转换负责将领域对象的数据映射到DTO中,只暴露需要的数据字段。

UI层:

UI层负责展示数据和接收用户输入,它不包含业务逻辑,只是通过调用Application层来触发业务流程。在这一层,主要形式有 api,job和视图页面等等

通过这些代码组织方式,你能够更好地体现领域的核心业务,降低不同层次之间的耦合度,使代码更加清晰、可维护和可扩展。随着深入掌握DDD思想,你将能够更灵活地应对复杂的业务需求,让项目架构更具弹性和可适应性。

领域驱动设计(DDD):三层架构到DDD架构演化的更多相关文章

  1. 如何使用ABP进行软件开发(2) 领域驱动设计和三层架构的对比

    简述 上一篇简述了ABP框架中的一些基础理论,包括ABP前后端项目的分层结构,以及后端项目中涉及到的知识点,例如DTO,应用服务层,整洁架构,领域对象(如实体,聚合,值对象)等. 笔者也曾经提到,AB ...

  2. 【DDD】领域驱动设计实践 —— 架构风格及架构实例

    概述 DDD为复杂软件的设计提供了指导思想,其将易发生变化的业务核心域放置在限定上下文中,在确保核心域一致性和内聚性的基础上,DDD可以被多种语言和多种技术框架实现,具体的框架实现需要根据实际的业务场 ...

  3. [转]DDD领域驱动设计基本理论知识总结

    领域驱动设计之领域模型 加一个导航,关于如何设计聚合的详细思考,见这篇文章. 2004年Eric Evans 发表Domain-Driven Design –Tackling Complexity i ...

  4. DDD领域驱动设计基本理论知识总结

    领域驱动设计之领域模型 加一个导航,关于如何设计聚合的详细思考,见这篇文章. 2004年Eric Evans 发表Domain-Driven Design –Tackling Complexity i ...

  5. 分享我对领域驱动设计(DDD)的学习成果

    本文内容提要: 1. 领域驱动设计之领域模型 2. 为什么建立一个领域模型是重要的 3. 领域通用语言(Ubiquitous Language) 4.将领域模型转换为代码实现的最佳实践 5. 领域建模 ...

  6. DDD领域驱动设计和实践(转载)

    -->目录导航 一. DDD领域驱动设计介绍 1. 什么是领域驱动设计(DDD) 2. 领域驱动设计的特点 3. 如果不使用DDD? 4. 领域驱动设计的分层架构和构成要素 5. 事务脚本和领域 ...

  7. DDD(Domain Driver Designer) 领域驱动设计简介

    领域驱动设计之领域模型 加一个导航,关于如何设计聚合的详细思考,见这篇文章. 2004年Eric Evans 发表Domain-Driven Design –Tackling Complexity i ...

  8. DDD(领域驱动设计)应对具体业务场景,Domain Model(领域模型)到底如何设计?

    DDD(领域驱动设计)应对具体业务场景,Domain Model(领域模型)到底如何设计? 写在前面 阅读目录: 迷雾森林 找回自我 开源地址 后记 毫无疑问,领域驱动设计的核心是领域模型,领域模型的 ...

  9. 我对领域驱动设计(DDD)的学习成果

    领域驱动设计之领域模型 2004年Eric Evans发表Domain-Driven Design – Tackling Complexity in the Heart of Software (领域 ...

  10. DDD领域驱动设计基本理论知识总结(转)

    领域驱动设计之领域模型 为什么建立一个领域模型是重要的 领域通用语言(UBIQUITOUS LANGUAGE) 将领域模型转换为代码实现的最佳实践 领域建模时思考问题的角度 领域驱动设计的经典分层架构 ...

随机推荐

  1. 2021-07-04:股票问题1。给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。你只能选择某一天 买入这只股票,并选择在未来的某一个不同的日子

    2021-07-04:股票问题1.给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格.你只能选择某一天 买入这只股票,并选择在未来的某一个不同的日子 ...

  2. 2021-06-18:已知数组arr,生成一个数组out,out的每个元素必须大于等于1,当arr[cur]>arr[cur-1]时,out[cur]>out[cur-1];当arr[cur]>arr

    2021-06-18:已知数组arr,生成一个数组out,out的每个元素必须大于等于1,当arr[cur]>arr[cur-1]时,out[cur]>out[cur-1]:当arr[cu ...

  3. .Net使用第三方onnx或ModelBuilder轻松接入AI模型

    ML.Net - 开源的跨平台机器学习框架 支持CPU/GPU训练 轻松简洁的预测代码 可扩展其他的机器学习平台 跨平台 1.使用Visual Studio的Model Builder训练和使用模型 ...

  4. ClickHouse进阶|如何自研一款企业级高性能网关组件?

    使用原生ClickHouse集群进行节点数据查询和写入时,离不开第三方开源网关组件chproxy支持.但由于chproxy缺少TCP协议支持,导致性能.查询能力等受限.这也成为困扰众多ClickHou ...

  5. Java中如何中断线程

    在Java中,可以使用以下方法中断线程: 1. 使用`interrupt()`方法:每个线程对象都有一个`interrupt()`方法,用于中断该线程.当调用线程的`interrupt()`方法时,它 ...

  6. Google Chrome 超详细使用教程

    由于微信不允许外部链接,你需要点击文章尾部左下角的 "阅读原文",才能访问文中的链接. 调查统计机构 NetMarketShare 发布最新的 7 月份报告,在全球浏览器市场,谷歌 ...

  7. 完成第一个 Vue3.2 项目后,使用体会

    第一次Composition API 在vue3.2中,正式支持了script setup的写法,这样可以大大简化组件的代码量,减少一些重复操作,我认为当你写vue3时,应该把这当作默认写法.在vue ...

  8. 9.3. Hibernate框架

    Hibernate是一个开源的持久层框架,它可以帮助我们将Java对象映射到数据库表中,并实现对象的持久化操作.Hibernate提供了丰富的API,可以方便地进行CRUD(增删改查)操作,而无需手动 ...

  9. C#.NET CORE .NET6 RSA 私钥签名 公钥验签(验证签名) ver:20230614

    C#.NET CORE .NET6 RSA 私钥签名 公钥验签(验证签名) ver:20230614 环境说明: .NET CORE 版本:.NET 6 . .NET CORE 对于RSA的支持: 1 ...

  10. 全球开源 AI 游戏开发挑战赛,只等你来!

    我们在之前的文章中 预告过 (*划重点,IP 属地法国):7 月初,我们将举办一次与 AI 游戏相关的黑客松活动,这是有史以来的首次开源游戏开发挑战赛,借助人工智能工具释放你的创造力,一起打破游戏开发 ...