Nestjsで動的なリマインド機能

komosyu
Nestjs

目次

  1. はじめに
  2. Nestjs のリマインダー機能とは?
  3. パッケージのインストール
  4. 基本をなめる
    1. @Cron
    2. @Interval
    3. @Timeout
  5. 動的なタイミングでリマインドをつくる
    1. リマインド機能
  6. 参考
  7. さいごに

はじめに

Nestjs を使ったプロジェクトで既に予約機能が用意されているところに追加で、その予約をしたことを忘れないようにユーザーに対して 30 分前にリマインドの通知を送りたい。という課題があったので、バックエンドの経験が浅い私はどう実装しようかと悩みながら調べてみたところ、Nestjs での方でそういったリマインダーの機能が用意されていたので、これを使って実装をして学んだことをドキュメントの例を交えながら、まとめていきます。

Nestjs のリマインダー機能とは?

私は冒頭でリマインダー機能と呼んでいましたが、ドキュメントの方ではTask Schedulingと呼ばれているようです。

基本的に実行できるタイミングはこちら

  • 固定した日時
  • 定期的な間隔
  • 指定した間隔を空けて 1 度

それ以外にも、今回実現したい機能のように動的なタイミングで実行できるDynamic APIなるものが用意されています。

具体的には@nestjs/scheduleというパッケージを利用して実装していきます。

パッケージのインストール

yarn add @nestjs/schedule
で OK。

基本をなめる

まずは基本的にどのような使い方をしていくのかを見ていきましょう!

Module@nestjs/scheduleからScheduleModuleimportしていきます。

import { Module } from '@nestjs/common'
import { ScheduleModule } from '@nestjs/schedule'

@Module({
  imports: [ScheduleModule.forRoot()], // forRootメソッドを実行
  controllers: [RemindController],
  providers: [RemindService],
})
export class RemindModule {}

forRootメソッドを実行することでスケジュールの初期処理が走って、これから紹介するデコレータが登録されます。

  • @Cron
  • @Interval
  • @Timeout

以下で簡単にまとめる

@Cron

指定したタイミングで処理を実行します。

  • 指定した日時に 1 度
  • 指定した定期的なタイミング(1 時間に 1 回、1 週間に 1 回、5 分に 1 回など)
import { Injectable, Logger } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';

@Injectable()
export class TasksService {
  private readonly logger = new Logger(TasksService.name);

  @Cron('45 * * * * *') // タイミングを指定したいメソッドにデコレータとして設定
  handleCron() {
    this.logger.debug('Called when the current second is 45');
  }
}

上記のコードでは毎分 45 秒になったタイミングで実行される。

オプションが設定できたりといろいろ設定ができたりするのですが、今回は Cron の引数の45 * * * * *ところだけ取り上げます。

* * * * * *
| | | | | |
| | | | | day of week
| | | | months
| | | day of month
| | hours
| minutes
seconds (optional)

*に月・週・曜日・時間・分・秒を当てはめてタイミングを調整できるようですね。

曜日とか細かく設定できるのは便利そうです。

また、JS のDateオブジェクトを受け入れることができるので、ここで動的にリマインドする機能を実装できるかも?と思ったのですが、これから説明するDynamic APIの方がさまざまなメソッドが用意されていたり管理しやすいな。という印象だし、元々動的に管理する目的のものなのでこちらで実装するのが正しそうです。

@Interval

定期的に指定された間隔で実行するもの
JS のsetIntervalのイメージ

@Interval(10000)
handleInterval() {
  this.logger.debug('Called every 10 seconds');
}

@Timeout

指定したタイミング 1 度だけ実行するもの
JS のsetTimeoutのイメージ

@Timeout(5000)
handleTimeout() {
  this.logger.debug('Called once after 5 seconds');
}

動的なタイミングでリマインドをつくる

お待たせしました。
ここからが本題です。

Dynamic APIを使って、いざリマインド処理を実装していきます!
簡単に説明すると、先ほど説明した@Cron,@Interval,@Timeoutをを文字通り動的に管理することができるものです。
今回は予約が入ったタイミングでのリマインドになるので、@Cronを使って実装をしていきます。

予約が入ったタイミングでタスクをつくっていくのに CronJob を使います。

yarn add cronをしてパッケージをインストールします。

import { CronJob } from 'cron'

そして、そのタスクを管理するのに@nest/scheduleからSchedulerRegistryを使ってスケジュールされたタスクの管理をしていきます。

import { CronJob } from 'cron';
import { SchedulerRegistry } from '@nestjs/schedule';

@Injectable()
export class RemindService {
    constructor(private schedulerRegistry: SchedulerRegistry) {}
}

まずこれで簡単に CronJob の作成とそこにアクセスしてみます。

import { CronJob } from 'cron';
import { SchedulerRegistry } from '@nestjs/schedule';

@Injectable()
export class RemindService {
    constructor(private schedulerRegistry: SchedulerRegistry) {}

    @Cron('* * 4 * * *', {
        name: 'remind', // ここで固有の値を設定
    })
    triggerReminds() {}

    getTasks() {
        const job = this.schedulerRegistry.getCronJob('remind'); // triggerRemindsでnameとして指定した"remind"のタスクたちを取得
        job.stop();
        console.log(job);
    }
}

作成したタスク(job)ではこんなことができます。

  • stop()
    実行がスケジュールされているジョブを停止します
  • start()
    停止されたジョブを再開します。
  • setTime(time: CronTime)
    ジョブを停止し、新しい時刻を設定してから開始します
  • lastDate()
    ジョブが実行された最後の日付の文字列表現を返します
  • nextDates(count: number)countmoment
    今後のジョブ実行日を表すオブジェクトの配列 (size )を返します

リマインド機能

すみません、それでは本当に本題の予約が入ったらリマインドを入れる機能を実装していきます。

import { CronJob } from 'cron';
import { SchedulerRegistry } from '@nestjs/schedule';

@Injectable()
export class RemindService {
    constructor(private schedulerRegistry: SchedulerRegistry) {}

    reservationRemind(data) { // dataは予約データが入る想定
        // 予約日時の30分前
        const jobTime = new Date(data.dateAndTime .getTime() - 30 * 60 * 1000);
        // 予約固有の値
        const jobName = data.uuid;
        // タスクの作成
        const job = new CronJob(jobTime, () => {
            // ここで例えばメールで通知を送るメソッドなどを実行
            this.sendRemindMail(data, jobName)
        })
        // リマインド
        this.schedulerRegistry.addCronJob(jobName, job)
        // 実行
        job.start()
        // タスクが追加されたか確認
        console.log(this.schedulerRegistry.getCronJobs());
    }

    sendRemindMail() {
        // メールを送る処理
        ~~~
        // 完了したタスクを削除
        this.schedulerRegistry.deleteCronJob(cronName);
    }
}

これで完成!!!
実装自体は非常にシンプルでしたね。

参考

さいごに

nest/scheduleを使ってのリマインド機能の実装おもしろかったですね。
理解して使いこなすことができれば、もっとさまざまな要件でも実装することができるようになると思うので、またそんなケースがあったらこちらで共有していきたいなと思います。
ありがとうございました!