awesometaro

レガシーなphpでキューイングシステム構築案を考えてみる

概要

レガシーなモノレポシステムでは、dockerでサービスが提供されていなかったり、オンプレで提供されていたり、さまざまな事情でworkerを用意できないことがあります。 workerを利用できたとしても、レガシーなシステムではキューイングのためのパッケージが利用できない場合があると思います。 今回はレガシーなphpなシステムでキューイングシステムを構築するためにはどうしたらいいかを考えてみます。

キューイングシステムとは

rails でいうsolid queueやsidekiq、symfonyでいうsymfony-messengerです。 いわゆるバックグランド非同期実行機構です。

使うと良さそうなもの

  • デザインパターンの1つのCommand Pattern
  • phpのserialize, unserialize
  • バッチ

ざっくりアイデア

  • キューを登録する時にはCommandInterfaceを実装したクラスのインスタンスを生成
  • serializeしてqueue_jobテーブルのjob_dateカラムに保存
  • バッチでX 分に1回、status = wait なqueue_jobテーブルをselect
  • 1 件ずつjob_dataカラムをunserialzeしてクラスインスタンスを復元し、executeメソッドを実行

※ セキュリティ的な観点で、phpのserialize, unserializeの利用は避けるべきですが、今回はアイデアを紹介するために利用します。実際にはjson_encode, json_decodeを利用するとともに復元できるCommandクラスのwhiteListなどを作るといいのかもしれません。

実装案

テーブル

create table job_queue (
    id INT,
    job_data TEXT,
    status VARCHAR(20)
)

実装

Commandクラス


class SendEmailCommand implements CommandInterface
{
    public function execute()
    {
        $this->mailer->send();
    }
}

interface CommandInterface
{
    public function execute();
}

Enqueueクラス(キューを保存する)

class Enqueue {


    public function execute(CommandInterface $command)
    {
        $job = new JobQueue(
            jobData: serialize($command),
            status: Status::WAIT
        );
        
        $this->JobQueueRepository->save($job);
    }
}

キューを取り出して実行する側: バッチで定期的に実行する


class ConsumeQueue
{
    public function execute()
    {
        foreach($this->JobQueueRepository->ofStatus(STATUS::WAIT) as $job) {
            $job->updateStatus(Status::PROCESSING);
            $this->JobQueueRepository->save($job);

            $jobCommand = unserialize($job->getJobData());
            $jobCommand->execute();
            
            $job->updateStatus(Status::SUCCESS);
            $this->JobQueueRepository->save($job);
            
        }
    }
}