发布于 2026-01-05 1 阅读
0

NestJS 和项目结构——该怎么做?

NestJS 和项目结构——该怎么做?

所以,你才刚开始接触 Nest,是吗?你可能还没完全理解为什么要使用 Nest,或者为什么它是全球最流行的 NodeJS 框架之一。但是,你肯定会说,“既然这么多人都在用,那它肯定不错”,对吧?

这篇文章将帮助你更好地理解为什么选择 Nest。

洋葱/清洁 -> Nest 架构

希望您了解洋葱架构整洁架构,或者对关注点分离的概念有基本的了解

此外,您还应该对领域驱动设计有一个大致的了解。特别是“领域”的含义(它并非指网站名称)。

看看这张图。
呼!是不是感觉信息量太大了?(摘自 https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/)哇!是不是很震撼?图片来自herbertograca.com

不要太纠结于这张图,但没错,这几乎就是你为了确保一个全栈应用程序能够正常运行(使用任何语言和任何框架)而必须兼顾的一切。

而且,Nest只是其中的一部分。确切地说,是“核心”部分。

由于 Nest 是系统的核心部分,它也涵盖了堆栈架构的某些“层”,即:

  • 通信层通过控制器(用于 REST)、解析器(用于 GraphQL)或网关(用于 WebSocket)来实现(微服务还有更多实现方式)。
  • 数据库抽象层,包含 ORM 存储库、ODM 模型或 Prisma 客户端
  • 包含服务的业务逻辑层
  • 带有防护措施的访问控制层
  • 带有管道的数据验证和转换层
  • CQRS/总线系统
  • 如果采用 MVC 架构,也包括表示层。

……还有更多。

关键在于,你的 Nest 应用可以做很多事情,而如何将所有这些功能合理地安排到合适的位置,同时还要对应用需要解决的整体业务领域问题进行建模,这本身就是一项艰巨的任务。在项目初期,经常会遇到这样的问题——我该如何构建我的 Nest 应用?

这个问题通常是因为你刚开始学习 Nest。然而,可以肯定的是,你对你的应用应该实现的功能已经有了相当清晰的构想。

……这真是一件好事,因为我们可以利用这一点来构建 Nest 应用程序!

但现在,你只需要记住,Nest 会为你构建最终的“洋葱式”或“简洁式”架构。这也是 Nest 采用模块化系统的原因之一(也是你需要向应用中添加大量额外代码的原因),同时也是 Nest 如此受欢迎的原因之一。从某种意义上说,这有点自相矛盾。

两个领域

正如刚才提到的,作为一名应用开发者,你需要兼顾两大领域。这两个领域分别是:

  • “开发者领域”——指关于如何在所使用的框架和语言(在本例中为 Nest 和 TypeScript/JavaScript)中使应用程序正常运行的知识。

以及

  • “业务领域”——关于应用程序应该如何解决您的业务问题的知识

使用 Nest 编写代码时,你必须将这两个领域融合在一起,但实际上,它们根本就无法融合。这又是一个悖论……

所以,问题就此产生……

我是否应该将代码组织成一个领域,即代码创建和框架构建,然后在其中的某个地方放入业务逻辑?

  • 或者 -

我是否应该尝试构建业务领域模型,并思考如何将开发领域融入其中?

你应该选择后者,Nest 的模块化系统可以为你简化这个过程。

领域、特性和模块

正如我们之前提到的,作为 Nest 的新手,您可能更了解您的业务领域,而不是开发领域。所以,从这个意义上讲,为什么不从您最熟悉的领域入手呢?

让我们继续推进新的 Nest 应用的开发。

如果还没有安装,请安装 Nest 全局 CLI并创建一个新项目。

Nest 应用的新文件夹结构

app.controller.ts这些app.service.ts只是示例,用来演示如何运行应用程序。您可以删除它们。app.module.ts但请保留它们,因为它们是您的“根”模块。

现在,让我们以你的业务领域为例,马上开始吧。假设你想开发一个类似 WordPress 的博客应用,但它将是 WordPress 的升级版,甚至比 WordPress 更强大、更出色!哈哈!我们都想称霸世界,对吧!


图片来源:DOOMSTEADDINER.ORG

好吧,好吧,也许不是。但可以肯定的是,您肯定希望解决业务领域的问题。所以,无论如何,您肯定知道自己想要什么。就我们的例子而言,它是一款博客应用程序。

问问自己,你希望你的博客应用具备哪些总体“功能”?当然,我们先从相对简单的开始。比如说……

博客管理:

  • 撰写博客
  • 发布博客
  • 在博客上发表评论

那么,用户管理方面呢?

用户管理:

  • 创建用户
  • 删除用户
  • 编辑用户
  • 显示用户

最后,如何才能让用户拥有正确的权限进入应用程序呢?

身份验证管理:

  • 用户登录
  • 注销用户
  • 注册用户
  • 设置用户权限

这很简单,对吧?

构建洋葱的层次

现在您可以使用 Nest CLI 的命令来构建您的应用程序。

让我们创建高级“管理”域。在项目文件夹中,运行以下命令:

nest generate module blog
nest generate module user
nest generate module auth
Enter fullscreen mode Exit fullscreen mode

注意发生了三件事。

  • 您有以这些域名命名的文件夹。
  • 您有名为这些名称的模块 TS 文件。
  • (更棒的是)这些模块已经导入到你的程序中了app.modules.ts。是不是很酷?

现在或许是展望未来的好时机。您可以继续创建管理功能的模块,但这可能有点过度设计,具体取决于实际需要编写多少代码才能实现这些功能。

你觉得 ` userand`auth模块应该比较简单,所以就没继续添加其他模块。但是对于 `with` 模块blog,嗯……你不太确定。它可能变得很棘手也很复杂,尤其是注释部分。所以,你决定继续在文件夹内操作blog

切换到该blog文件夹​​并运行以下命令:

nest generate module drafting
nest generate module publishing
nest generate module commenting
Enter fullscreen mode Exit fullscreen mode

最终你会得到下图,而且这些模块会自动注册。blog.module.ts

接下来我们来看auth模块……呃……领域,嗯……管理。(看到了吗?领域驱动设计(DDD)的东西又融入进来了。)

好的。我们只需要三个流程:登录、注销和注册。理论上,我们只需要三个方法,但这些方法可能会变得比较复杂。因此,我们决定为每个流程创建单独的类。切换到该auth目录并运行以下命令:

nest generate service login
nest generate service logout
nest generate service register
Enter fullscreen mode Exit fullscreen mode

你现在拥有这些:

此外,这些服务会自动注册到auth模块中。真棒!

希望你现在已经明白了。

但是,我们需要更多东西才能实现这个auth功能。要不加个控制器,用来把调用路由到服务?控制器应该尽量精简,这样才能确保所有东西都能放在一个类里。

nest generate controller auth --flat
Enter fullscreen mode Exit fullscreen mode

注意这个--flat参数。它告诉 Nest 不要创建auth以控制器命名的文件夹。它将控制器文件放在当前目录中。此外,控制器会自动注册到auth.module.ts.

好了,我们有了控制器,接下来呢?

我们已经了解了守卫机制,因此也知道我们需要某种守卫机制来保护未来 API 中的某些其他路径。让我们来创建它。

nest generate guard auth --flat
Enter fullscreen mode Exit fullscreen mode

我们的守卫已创建完成。但请注意,守卫不会自动注册到您的模块中。

我们可以继续创建更多 Nest 提供程序,例如添加 ORM 文件(存储库、实体)、创建 DTO 类、验证管道以及其他许多东西,但你现在很可能已经明白我的意思了。

在这个练习中需要注意的关键点是,我们将架构的所有层都放在一个模块(一个迷你洋葱)中。编译时,Nest 会将所有模块聚合起来,生成最终的应用程序(最终的强大洋葱)。

这种模块化系统有一些非常好的优点:

  1. 基本上,你可以把任何模块(稍作修改后)复制到其他地方重复使用。代码重用真是太棒了!

  2. 你会获得一个强大的依赖注入系统。我们甚至还没开始讨论这个话题,但这样的系统对于企业级代码库来说至关重要,而且在某种程度上,它关系到你的工作效率。

  3. 当您遇到功能问题时,可以快速找到需要处理的代码。例如,假设您遇到了博客输入验证方面的问题。您只需前往相应的/blog/draft文件夹,就能找到定义验证规则的 DTO 或执行验证的管道。所有代码都集中在一起,无需在海量文件中苦苦寻找。

  4. 最后,Nest 的模块化设计会让你更倾向于面向服务的思维方式。反过来,这又能帮助你更轻松地过渡到微服务架构。记住,我们当初的目标是统治世界!对吧?哈哈!

玩笑归玩笑。基本上,你可以说 Nest 的作用在于引导开发者遵循模块化流程,从而避免干扰你的工作,而 Nest 本身则扮演着辅助角色。它只是实现目标的手段,你的领​​域问题才是最重要的(或者说,它应该是最重要的)。你面临的业务问题应该决定你如何构建应用程序。Nest 只是提供了一些便捷的工具来实现这一点。

嗯……好像少了点什么!

你可能会说:“等等!应用程序中那些不属于任何业务领域,而只属于开发人员领域的东西呢?比如连接数据库、通过 Swagger 生成 API 或配置?”

问得好!

core域……呃……咳……模块怎么样?

切换到根目录并运行:

nest generate module core
Enter fullscreen mode Exit fullscreen mode

我们再为上面提到的事项创建一些服务。返回到该core文件夹​​并运行:

nest generate module database
nest generate module swagger
nest generate module config
Enter fullscreen mode Exit fullscreen mode

好了!(注:图片显示的是 common,因为我最初使用了 common,但这个模块应该命名为“core”。Common 应该用于像实用程序这样常用的共享资源。)

现在你可以用逻辑的方式解决问题了。

结论

我希望这篇文章能让你更好地理解为什么 Nest 的模块系统对你的开发工作如此强大。Nest 还有许多其他功能,让使用体验非常愉快。没错,它一开始看起来可能有点冗长。但是,现在你知道原因了。一旦你越来越多地使用 Nest,你就会惊叹以前没有它的时候,你是怎么开发后端应用程序的。

尽情享受 Nest 带来的乐趣吧!

补充说明:在 Nest Discord 服务器上再次帮助一位用户解决项目结构问题后,我意识到 Nest 模块的另一个优势。在我们的博客示例中,我们使用了更高级别userauth模块。实际上,这些模块可以被视为“任何应用中我始终需要的东西”。我之前提到过代码重用,如果您知道这些模块总是会一起添加到任何应用中,那么您可能希望最终得到一个“核心”模块,其中包含诸如“auth”、“authz”和“user”之类的其他模块。这样做的好处在于(这也是它的优势所在),您可以非常轻松地重构它。创建一个“核心”模块,将文件夹移动到该模块中,并将它们注册为导入项,如果需要,还可以将其拆分为一个独立的库,供您在其他应用中使用。是不是很棒?🙂

补充说明 2:我经常看到有人尝试将“领域驱动设计 (DDD)”融入 Nest 项目的文件结构中。Nest 的灵活性足以允许这样做,但是,最终会遇到两个问题。

  1. 实际上,解决问题需要仔细检查文件系统中那些你认为“适合”你的应用遵循“领域驱动设计”(DDD)的层级。然而,如果你像 Nest 建议的那样,将 DDD 保留在每个模块中,那么你只需要进入相应的模块来解决问题即可。

  2. 更严重的问题是,你破坏了 Nest 的模块系统,从而破坏了将代码/模块作为库重用的能力。对于新手来说,这或许不算什么大问题,但当你意识到你可以将大量代码复制到新的或其他 Nest 项目中时,问题就来了。

在我看来(这只是我个人的观点),领域驱动设计(DDD)应该指导你如何思考你的应用程序,而不是如何设计文件系统/项目结构。当你在实际应用中遇到问题时,无论是需要改进还是修复应用程序,都不应该需要根据 DDD 重新考虑代码的局部性。那样只会浪费大量时间。使用 Nest 的“模块化方法”。这才是你应该遵循的标准。在处理整体问题时,要牢记 DDD,但不要让它影响代码结构。这完全没有必要。

补充说明3:我今天偶然看到了这篇文章。我认为它有助于佐证我以上的观点。

https://medium.com/@stevebishop_89684/clean-architecture-is-not-a-project-structure-b158c9c4163f

文章来源:https://dev.to/smolinari/nestjs-and-project-struct-what-to-do-1223