当前位置: 首页 > news >正文

从 Spring Boot 到 NestJS:模块化设计的哲学差异

模块思想的显式和隐式

隐式是我以前开发接触的(java后端开发)

但现在我的开发(nestjs后端开发)是显式的了

所以就诞生了我下面遇到这个问题。

先说一下我项目的结构

[ controller -> endpoint ] -> [ service -> repo ]

Apis层                                  Core层

今天犯了个架构上的问题,我的“核心业务(Core)”层(比如处理数据库的 Repository)反过来依赖了我的的“展示(API)”层(比如 DTOs)。

就是在repo接受参数以及return的时候,用了api的params和response。

这就像是发动机(Core)依赖了汽车的“油漆颜色”(API)的定义。这是本末倒置的。

详细解释:依赖关系搞反了

在一个标准的“分层架构”中,依赖关系应该是单向的,并且总是指向核心

[ API 层 ] (例如: Controllers, DTOs) 它依赖 Core 层----->[ Core 层 ] (例如: Services, Repositories, Entities) 它依赖 Shared 层----->[ Shared 层 ] (例如: 工具类, 常量)

  • API 层 (展示层):负责接收 HTTP 请求、发送响应。DTOs (Data Transfer Objects) 通常在这里定义,用来规定API 接口的数据格式
  • Core 层 (核心业务层):负责所有的业务逻辑。Repository (仓库) 是这一层里专门负责与数据库交互的部分。

我遇到的问题是: 我的 Core 层(RepositoryimportAPI 层(DTOs)。这就建立了一个反向的依赖Core ---> API),打破了架构规则。


为什么这是个问题?(紧密耦合)

  1. 易碎性Core 应该是我系统中最稳定、最核心的部分。API 则是最常变动的部分(比如我为了前端方便,想改一个 DTO 字段的名字)。
  • 现在的后果:你一旦修改了 API 层的 DTO(比如改个字段名),你的 Core 层(Repository)代码就可能编译失败,你被迫也要去修改核心代码。

    2. 可重用性差Core 层的业务逻辑应该可以被重用。
  • 举个例子:如果你想增加一个**命令行工具(CLI)**来执行某些业务,它也应该调用 Core 层。但现在 Core 依赖了 API 层的 DTO,这个 DTO 是为 Web API 设计的,CLI 根本用不了。

如何修复(错误信息给的建议)

错误信息给了你两个解决方案:

方案 1:将 DTOs 移到 Core 层
  • These DTOs should be moved to the Core layer
  • 做法:把这些 DTOs 文件从 API 目录移动到Core 目录。
  • 含义:这表示你承认“这些 DTOs 并非只给 API 用,而是我核心业务就认可的数据结构”。这样 API 层和 Repository(都在 Core 层)就都可以合法地导入和使用它们。
方案 2:Repository 应使用 Core 层的类型
  • ...or the repository should use Core-layer types
  • 做法:这是一种更“纯净”的架构。
  1. API 层保留自己的 DTOs(例如 CreateUserRequestDto)。
  2. Core 层定义自己的内部模型实体(例如 UserEntityUserModel)。
  3. Repository (在 Core 层) 只接收和返回 UserEntity
  4. API 层(Controller)在调用 Core 之前,有责任DTO转换 (map)Core 层的 UserEntity

总结: 这个错误的本质是架构的“隔离性”被破坏了。你的核心代码(Repository)不应该知道 API 层(DTOs)的任何实现细节。

继续延伸一下这个思想

那为什么nestjs会用这种分模块的概念呢,不能跟java一样吗

事实上,NestJS 的架构理念和现代 Java(尤其是 Spring Boot)惊人地相似。它们都严重依赖依赖注入 (DI)面向切面编程 (AOP)模块化

我感觉到的“不一样”,主要来自于 NestJS 强制你“显式”地定义模块,而 Java (Spring Boot) 更多地依赖**“隐式”的组件扫描**

我熟悉的 "Java (Spring Boot) 方式":隐式组件扫描

在一个典型的 Spring Boot 项目中,你通常会:

  1. 在主类上放一个 @SpringBootApplication
  2. 这个注解(Annotation)包含了 @ComponentScan
  3. @ComponentScan自动扫描你项目中所有的包(package),查找所有标记了 @RestController, @Service, @Repository 的类。
  4. 它把所有找到的类都注册到一个全局的、单一的依赖注入容器中。
  5. 当你需要依赖时,你使用 @Autowired,Spring 会从这个全局容器中找到并注入它。

这种方式非常“神奇”且快速,你不需要“注册”任何东西。但当项目变得非常庞大时,它会带来一个问题:缺乏清晰的边界。任何服务都可以 @Autowired 几乎任何其他服务,导致依赖关系混乱,难以维护(有时被称为“全局依赖地狱”)。


"NestJS 方式":显式的模块定义

NestJS 深受 Angular 的启发,它采用了显式的模块化系统 (@Module)。

在 NestJS 中,每个模块(Module)都是一个“黑盒”或“微型容器”

  • providers: 模块内部的服务 (Service) 和仓库 (Repository)。默认情况下,它们是私有的,只能在该模块内部使用。
  • controllers: 模块对外暴露的 API 接口。
  • imports: 该模块需要从其他模块导入哪些服务。
  • exports: 该模块允许哪些内部的 providers 被其他模块使用。

为什么 NestJS 要这样做?

NestJS 选择这种“显式”的方式,而不是 Java (Spring) 的“隐式”方式,主要是为了在大型应用中强制实现更好的架构

  1. 强制封装 (Strong Encapsulation) ⭐️ 这是最重要的原因。providers 默认是私有的。如果你不把一个服务(比如 UserService)放在模块的 exports 数组中,其他模块绝对无法注入它。这可以防止你写出“意大利面式”的代码,避免不相关的模块随意互相调用。
  2. 清晰的依赖关系图 (Clear Dependency Graph) 你不需要工具就能看懂架构。你只要打开 app.module.ts,查看 imports 数组,就能立即知道你的应用程序由哪几个核心模块构成,以及它们之间的依赖关系。一切都是显式声明的,没有“魔法”。
  3. 避免全局污染 (Avoiding Global Hell) 在大型 Spring 项目中,你可能会遇到两个同名的 UserService 类(来自不同包),导致 @Autowired 冲突或注入了错误的实例。在 NestJS 中,这种冲突几乎不会发生,因为依赖是按模块隔离的。
  4. 可测试性 (Testability) 当你想为 OrdersModule 编写测试时,你不需要加载整个应用的所有服务。你只需要在测试环境中导入 OrdersModule 和它显式import 的几个模块,测试会更轻量、更快速。
  5. 性能 (Performance) 和懒加载 (Lazy Loading) 这种显式的模块边界使得 NestJS 可以轻松实现模块的懒加载。例如,一个很少被访问的“后台管理模块”可以配置为在第一次被请求时才加载,加快应用的启动速度。

总结

所以,NestJS 和 Java (Spring) 一样都在实践模块化,但:

  • Java (Spring) 倾向于:隐式的、全局的组件扫描(@ComponentScan)。
  • NestJS 倾向于:显式的、隔离的模块定义(@Module)。

NestJS 的方式在刚开始时会让你觉得“繁琐”(要写 imports, exports),但这种“繁琐”换来的是极高的可维护性、清晰的架构边界和更少的意外,这在需要长期维护的大型项目中是至关重要的。

http://www.dtcms.com/a/573560.html

相关文章:

  • WebSocket 使用
  • 郑州网络营销网站app上架应用市场需要什么条件
  • 百度网站官方认证怎么做郑州网站建设贴吧
  • Spring定时任务cron表达式解析
  • 做网站通过什么赚钱wordpress 主题 下载
  • MATLAB视觉检测系统详细介绍
  • 网络工程基础
  • 【NXP i.MX91】 RT-Linux移植
  • 怕随身 WiFi 虚量断连?格行随身wifi拆箱测评:1500G 真不虚标?
  • 门户网站建设摘要强大的wordpress瀑布流主题
  • Kubernetes1.23版本搭建(三台机器)
  • 远程桌面工具汇总:RustDesk、1Remote、CrossDesk
  • linux下动静态库
  • iss服务器网站建设防止网站流量被刷
  • 【机器学习16】连续状态空间、深度Q网络DQN、经验回放、探索与利用
  • 网络传输协议的介绍,HTTP、SSE、WebSocket
  • 上海做网站公司有哪些北京网站建设公司哪家实惠
  • iOS 基于 Foundation Model 构建媒体流
  • Zabbix 6.0 基于 LNMP 架构完整部署教程(CentOS7)
  • 接口自动化测试----高并发抽奖系统
  • 用Python来学微积分31-定积分的概念与几何意义详解
  • 使用 Python 语言 从 0 到 1 搭建完整 Web UI自动化测试学习系列 23--数据驱动--参数化处理 Yaml 文件
  • 基于SpringBoot的公务员考试管理系统【题库组卷+考试练习】
  • Nginx 反向代理 HTTPS CDN 配置检查清单(避坑版)
  • 网站套餐到期是什么意思减压轻松网站开发
  • 常见的矩阵运算方法与应用
  • SQLite 3.51.0发布,新功能解读
  • 贺州网站推广网站设计报价是多少
  • 网站信息备案变更 哪里做seo工具是什么意思
  • 【TiDB 插入性能优化实战:从 5 秒到毫秒级的跨越】