Laravelで非同期処理を含めたバッチ処理実装

Laravelバッチ処理プログラミング

Laravelの非同期処理を含めたバッチ処理についてまとめます。

例えば、ユーザーの誕生日になったらお祝いメールを送信するといった機能があったとします。

ただ、一人ひとり手動でその日になったらメールを送信するなんて面倒くさいですよね。

そんなときにバッチ処理を使い、誕生日のユーザーが存在するかを毎日確認して、存在すればメールを送信するといったことを自動でやってくれたら嬉しいですよね。

また、メール送信など時間がかかる処理については非同期で行うことで他の処理に影響でないようにしてあげると尚良いですね。

この記事で書いてあること

・キューとジョブの理解
・非同期処理を含めたバッチ処理実装
・ジョブ、コマンド、タスクスケジュールの作成、設定方法

メール送信内容や細かい実装については省略している部分もありますのでご了承ください。

やりたいこと

・毎日、誕生日のユーザーが存在するかを確認
・メールは非同期で送信
・プレゼント付与
・上記を自動化

環境

・PHP 7.4
・Laravel 7.*
・MySQL 5.7

キューとジョブの作成

キューとジョブの関係

最初にキューとジョブの関係についてざっくり説明します。

ジョブはある任意の処理であり、キューは処理が実行されるのを待っているジョブをリストです。

日常生活で例えると、お買い物をするときにレジで行う会計処理がジョブで、レジ待ちで並んでいる列がキューになります。

いろんなとこでこういう光景は見られますね。

では、キューを利用すると何がいいのでしょうか?

キューのある世界とない世界を比べてみました。

PHPは基本的に直列処理を行う言語のため、キューのない世界のようにメール送信が完了してからプレゼント付与の処理が実行されます。

一方、キューのある世界ではメール送信処理が完了するのを待たずに、プレゼント付与を実行することができます。

非同期で処理することで他の処理が待たされることがないのでアプリケーションがフリーズすることがありません。

LaravelのキューサービスはデータベースやRedis、Amazon SQSなどのドライバが対応しています。

今回の例ではデータベースを使用します。

下記のコマンドによりジョブを保存するためのテーブル(jobsテーブルとfailed_jobsテーブル)が作成されます。

php artisan queue:table
php artisan migrate

ジョブの作成

では、メール送信処理を非同期で実行するためのジョブを作成します。

ジョブはphp artisan make:job クラス名を実行すると、app/Jobsディレクトリに保存されます。

class SendMail implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    private User $user;

    /**
     * 新しいジョブインスタンスの生成
     *
     * @return void
     */
    public function __construct(User $user)
    {
        $this->user = $user;
    }

    /**
     * ジョブの実行
     *
     * @return void
     */
    public function handle()
    {
        // ユーザーに対してメール送信する処理
        Mail::to($this->user->email)->send(new SampleMail());
    }
}

handleメソッドがキューでジョブを処理するときに呼び出され、中の処理が実行されます。

※今回はメールの中身(SampleMailクラス)についての処理は割愛いたします。

Artisanコマンド生成

次に、誕生日のユーザーを確認して、メールを送信しプレゼントを付与するという処理を呼び出すためのコマンドを作成します。

LaravelにはArtisanと呼ばれるコマンドラインインターフェースが存在します。

ご存知だと思いますが、php artisan migrateなどLaravelを使っていれば必ず使用するコマンドですね。

Artisanコマンドはphp artisan listですべてのコマンドを確認することができます。

【余談】

Artisanはフランス語で職人っていう意味なんだって〜

ちなみにLaravelはナルニア国物語に登場するケア・パラベルという王都の名前から来ているらしいよ

https://ja.wikipedia.org/wiki/Laravel

php artisan make:command クラス名を実行するとapp/Consoleディレクト配下にCommandsディレクトリが作成され、その中に作成したコマンドクラスのファイルが作成されます。

下記が今回の例をもとに作成したクラスです。

class BirthdayMail extends Command
{
    /**
     * コンソールコマンドの名前と引数、オプション
     *
     * @var string
     */
    protected $signature = 'send:birthday-mail';

    /**
     * コンソールコマンドの説明
     *
     * @var string
     */
    protected $description = '誕生日のユーザーにメールを送信します';

    /**
     * 新しいコマンドインスタンスの生成
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * コンソールコマンドの実行
     *
     * @return int
     */
    public function handle()
    {
        // タスク実行の日付が誕生日のユーザーを取得
        $users = User::where('birthday', CarbonImmutable::now())
            ->get();

        // 誕生日ユーザーがいなければ終了
        if (isEmpty($user)) {
            return 0;
        }

        // 取得したユーザーに対してメール送信(非同期で実行)
        $users->map(fn(User $user) => SendMail::dispatch($user));
        プレゼント付与する処理...
        return 0;
    }
}

handleメソッドの中の記述が$signatureプロパティで指定したコマンドを実行したときに行う処理です。

今回の例で言うと、下記の処理ですね。

  • 実行した日付が誕生日のユーザーを取得
  • 取得したユーザーに対して非同期でメール送信
  • プレゼント付与

dispatchメソッドに渡す引数はジョブクラス(SendMailクラス)のコンストラクタへ渡されます。

ここまでの実装でphp artisan send:birthday-mailを実行すると、handleメソッドの中に書いた処理が実行されますが、このままだと定期実行にはなりません。

タスクスケジュール設定

最後に上記で実装したArtisanコマンドとジョブを自動で毎日定刻に実行する設定をします。

Laravelにはスケジューラが存在しており、設定した時間に定期的に処理を実行してくれます。

まず、スケジューラを使用するためにはサーバにcronの設定を行います。

* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1

path-to-your-projectにはLaravelをインストールしたディレクトリを設定します。

このcronはLaravelのコマンドスケジューラを毎分呼び出し、スケジュールされている処理を実行します。

スケジュールはすべてapp/Console/Kernel.phpscheduleメソッドの中で定義します。

class Kernel extends ConsoleKernel
{
    /**
     * アプリケーションで提供するArtisanコマンド
     *
     * @var array
     */
    protected $commands = [
        BirthdayMail::class
    ];

    /**
     * アプリケーションのコマンド実行スケジュール定義
     *
     * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
     * @return void
     */
    protected function schedule(Schedule $schedule)
    {
        // 毎日9時に実行します
        $schedule->command('send:birthday-mail')
            ->dailyAt('9:00');
    }
}

今回の例ではsend:birthday-mailコマンドを毎朝9時に実行するように設定しました。

タスクを実行するタイミングについてはいろいろと設定が可能です。

詳しくは公式ドキュメントに載ってます。👇

タスクスケジュール 7.x Laravel

これによって、冒頭のやりたいことで書いたことを実装できました。

・毎日、誕生日のユーザーが存在するかを確認
・メールは非同期で送信
・プレゼント付与
・上記を自動化

今回はメール送信対象のユーザー数は意識しませんでしたが、例えばユーザー数が数千件とかになるとリソースに負荷がかかってしまうので100件ずつに分けて実行するなど考慮は必要になってきそうです。

参考資料

コメント

タイトルとURLをコピーしました