NestJS小技巧31-在NestJS中运行一次性操作

csharp 复制代码
by 雪隐 from https://juejin.cn/user/1433418895994094
本文欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可联系授权

原文链接

最近,在工作中我不得不根据一个外部服务产生的外部ID来更新一些生产数据库的列。使用一次性操作进行数据操作的两个较大的优点(在我看来)是:1)你不需要手动访问生产数据库,2)你不需要将模式迁移与数据迁移混合。

您可以在以下仓库中找到关于这篇文章主题的更完整示例。

在商业中,我们有时需要在我们的后端或数据库中运行特定操作,且只运行一次。有些人称这些操作为一次性操作,因为它们应该只运行并完成一次。运行一次性操作具有巨大的威力,因为它允许使用生产数据执行某些操作(例如向特定用户群发送电子邮件)或根据外部服务更新生产数据。最近,我在工作中需要根据外部服务产生的外部ID更新一些生产数据库列。使用一次性操作处理数据的两个较大优势(在我看来)是:1)您不需要手动访问生产数据库,以及2)您不需要将模式迁移与数据迁移混合在一起。

目前,我正在使用NestJS工作,但我找不到如何使用它运行一次性操作的任何文档。我们决定创建工具来运行我们的一次性操作,这篇文章将描述实现这个想法所需的步骤。为了文章的目的,让我们想象一个场景,我们想要使用某种电子邮件服务向我们的管理员用户发送电子邮件。

为一次性操作创建一个触发器

在此需要考虑的一点是,我们很可能需要在代码运行时在生产环境中运行这个一次性操作。这意味着我们需要在运行时某种方式触发这个一次性函数。我们考虑了两种方案:一个我们可以通过Curl调用的特定端点,或一个NestJS的新入口点。创建一个新的端点会将其暴露给公众,需要一些安全措施来防止未经授权的访问,所以我们决定采用后者。当你开始开发一个NestJS应用程序时,通常会为你创建一个入口点文件,叫做main.ts 。这个文件加载你的服务器运行所需的所有必要模型、控制器和服务依赖关系。我们不需要所有这些来运行这个一次性操作,所以我们将创建一个新的入口点,只带有必要的依赖关系。我们称之为one-off.ts

ts 复制代码
// one-off.ts  
  
import { NestFactory } from '@nestjs/core';  
import { OneOffModule } from './one-off.module';  
  
async function oneOff() {  
  const application = await NestFactory.createApplicationContext(OneOffModule);  
  await application.close();  
}  
  
oneOff().catch(console.error);

最小的依赖注入

上面的示例显示了一个尚未定义的 OneOffModule。这个 NestJS 模块将包含运行 one-off 所需的所有依赖项。在我们的情况下,我们需要访问数据库并使用将发送电子邮件的服务。作为参考,您的 main.ts 文件加载了一个不同的模块,通常称为 AppModule,它加载了您应用程序的所有依赖项。将两个模块分开的原因是,我们不希望我们的 one-offs 因加载真实生产应用程序所拥有的所有依赖而导致加载时间增加。

ts 复制代码
// one-off.module.ts  
  
import { TypeOrmModule } from '@nestjs/typeorm';  
import { Module } from '@nestjs/common';  
import { SendEmailToAdmins } from './one-offs';  
import { EmailModule } from './email/email.module';  
  
@Module({  
  imports: [  
    EmailModule,  
    TypeOrmModule.forRoot({  
      type: 'mysql',  
      host: process.env.DB_HOST,  
      port: process.env.DB_PORT,  
      username: process.env.DB_USER,  
      logging: false,  
      password: process.env.DB_PASSWORD,  
      database: process.env.DB_NAME,  
      synchronize: false,  
    }),  
  ],  
  providers: [SendEmailToAdmins],  
  exports: [SendEmailToAdmins],  
})  
export class OneOffModule {}

如您所见,我们只初始化了一个到数据库的连接(我使用的是TypeORM,但您可以使用自己的库),并仅提供了在这种情况下我们想要运行的唯一的 one-off,即 SendEmailToAdmins 服务。

运行命令

我们为这个工具设置的一个要求是我们可以随时运行它,并运行特定的一次性操作。我们为了实现这一点首先创建了one-off.ts入口点。现在,经过编译后,我们可以简单地运行 node dist/one-off.js 来加载和运行这个入口点,但是目前的代码写法并不允许我们运行特定的一次性操作。为了实现这一点,我们需要告诉程序要运行哪个服务。为此,我们结合了使用Node的process.argv数组来获取在终端中传递的参数和使用命名导出的强大功能来查找、加载和运行一个特定的服务。

ts 复制代码
// one-off.ts  
  
import { NestFactory } from '@nestjs/core';  
import { OneOffModule } from './one-off.module';  
import * as Executables from './one-offs';  
  
async function oneOff() {  
  const klass = process.argv[2];  
  const application = await NestFactory.createApplicationContext(OneOffModule);  
  const oneOff = application.get(Executables[klass]);  
  
  try {  
    await oneOff.run();  
  } catch (error) {  
    console.error(error);  
  }  
  
  await application.close();  
}  
  
oneOff().catch(console.error);

如你所见,传递给终端的值随后用于 Executables 对象,该对象持有由一次性模块导出的所有服务。这增加了你被允许运行的安全性。此外,因为我们希望未来的开发者更容易地创建更多的一次性操作,所以我们为 OneOff 类定义了一个接口,其中 run 是唯一的公共方法。

如果你正在使用像 yarn 这样的工具,你只需在 package.json 的脚本中添加这样的内容: "one-off": "node dist/one-off.js",然后你可以用 yarn run one-off SendEmailToAdmins 来调用它。对于像 Heroku 这样的 PaaS,你可以使用 Heroku CLI 的 run 命令从本地机器运行这些命令。到目前为止,我们已经很成功,不再需要手动运行数据迁移,或者在生产中使用模式迁移工具。

未来的工作

到目前为止的工具非常有用,尽管我一直在考虑一个小的改进 。现在,没有任何东西禁止我通过使用Heroku的run命令并多次使用相同的参数来多次运行相同的一次性任务。防止这种情况的一种方法是向数据库添加一个新表,就像架构迁移工具那样,并跟踪已经运行的一次性任务。这将允许我们只运行一次性任务,并保留已经运行的一次性任务的某种历史记录。这对于审计目的非常有用。

总结

在这篇文章中,我们探讨了如何在NestJS中创建一个运行一次性任务的工具。我们看到了如何创建一个新的入口点,如何加载运行一次性任务所需的依赖项,以及如何使用终端运行特定的一次性任务。我们还看到了如何使用Heroku的CLI在生产中运行这些一次性任务。这种方法的明显优势是,我们不需要手动访问生产数据库,也不需要将架构迁移与数据迁移混合在一起。在行业中,开发者体验正越来越受到重视,我认为这是一个很好的例子,说明了如何改进它,不仅在于拥有完成繁重任务的工具,还在于记录开发努力和审计的方面。

本章代码

代码

相关推荐
LCG元23 分钟前
Vue.js组件开发-如何实现异步组件
前端·javascript·vue.js
Lorcian25 分钟前
web前端12--表单和表格
前端·css·笔记·html5·visual studio code
问道飞鱼30 分钟前
【前端知识】常用CSS样式举例
前端·css
wl851135 分钟前
vue入门到实战 三
前端·javascript·vue.js
ljz20161 小时前
本地搭建deepseek-r1
前端·javascript·vue.js
爱是小小的癌1 小时前
Java-数据结构-优先级队列(堆)
java·前端·数据结构
傻小胖2 小时前
vue3中Teleport的用法以及使用场景
前端·javascript·vue.js
wl85112 小时前
Vue 入门到实战 七
前端·javascript·vue.js
Enti7c3 小时前
用 HTML、CSS 和 JavaScript 实现抽奖转盘效果
前端·css
LCG元3 小时前
Vue.js组件开发-使用Vue3如何实现上传word作为打印模版
前端·vue.js·word