Sidekiq ミドルウェア [翻訳]

この記事は、[Middleware - github.com/mperham/sidekiq](https://github.com/mperham/sidekiq/wiki/Middleware)の翻訳です。

----

# Sidekiq ミドルウェア

最新バージョン 2019/02/27 編集者 Nate Salisbury

Sidekiqには、Rackと同様のミドルウェアの概念があります。この仕組みを利用することで、小さな機能を実装してSidekiqに追加することができます。Sidekiqは、ミドルウェアとして見たときにクライアントとサーバーの2つの側面があります。

- **クライアントミドルウェア** ジョブをRedisに入れる前の段階で動きます。この仕組みを利用することで、ジョブがRedisに送られる前に、ジョブを 修正/ストップ することができます。

- **サーバーミドルウェア** ジョブが実行される前後で動きます。

## カスタムミドルウェア

Sidekiqのバージョン5以降では、最初から付属するミドルウェアはありません。様々なGemやサービスを使って、ジョブを追跡したり追加の機能を付与するようなミドルウェアをインストールすることができます。また、あなた自信でミドルウェアを追加することもできます。

### クライアントミドルウェア

クライアントミドルウェアを使用することで、Redisにジョブを入れる前に、ジョブにメタデータを追加することができます。追加されたメタデータは、サーバーミドルウェアでジョブが実行される前に利用することができます。例えば、現在のロケール、マルチテナントアプリでの現在のテナント、などが利用例として挙げられます。

クライアントミドルウェアは、クラスオブジェクトまたはクラス名を含む文字列を引数として受け取ります。

`yield` を呼ばなかったり、falseまたはnilを返すと、それ以上ミドルウェアは呼び出されません。そして、ジョブがキューにプッシュされることもありません。

```ruby
class Middleware::Sidekiq::Client::CustomerJobAttribute
  # @param [String, Class] worker_class エンキューされる予定のクラス名の文字列またはクラスオブジェクト
  # @param [Hash] job ジョブのペイロード
  #   * @see https://github.com/mperham/sidekiq/wiki/Job-Format
  # @param [String] queue ジョブが取り出されたキューの名前
  # @param [ConnectionPool] redis_pool Redisのコネクションプール
  # @return [Hash, FalseClass, nil] もしfalseまたはnilを返すと、
  #   ジョブはRedisにエンキューされません。そうでない場合は、ブロックの戻り値が
  #   そのままこのメソッドの戻り値になります。
  # @yield ミドルウェアチェーンの次のミドルウェア、または、ジョブのエンキュー
  def call(worker_class, job, queue, redis_pool)
    # return false/nil to stop the job from going to redis
    return false if queue != 'default'
    job['customer'] = Customer.current_id
    yield
  end
end
```

### サーバーミドルウェア

サーバーミドルウェアを使用することで、ジョブが実行される前後に任意の処理を実行することができます。下記のコードでは、ジョブで起きた例外のログを取っています。

`yield`を呼ばなかった場合は、それ以上ミドルウェアは呼び出されません。そして、ジョブの `perform` メソッドは実行されません。

```ruby
class Middleware::Sidekiq::Server::ErrorLogger
  # @param [Object] worker ワーカーインスタンス
  # @param [Hash] job ジョブのペイロード
  #   * @see https://github.com/mperham/sidekiq/wiki/Job-Format
  # @param [String] queueジョブが取り出されたキューの名前
  # @yield ミドルウェアチェーンの次のミドルウェア、または、ワーカーのperformメソッド
  # @return [Void]
  def call(worker, job, queue)
    begin
      yield
    rescue => ex
      puts ex.message
    end
  end
end
```

ミドルウェアのinitializeメソッドを利用することで、ミドルウェアにオプションを渡すこともできます。このオプションは、ミドルウェアの登録時に生成します。

```ruby
class Middleware::Sidekiq::Server::ErrorLogger
  def initialize(options=nil)
    # options == { :foo => 1, :bar => 2 }
  end
  def call(worker, msg, queue)
    begin
      yield
    rescue => ex
      puts ex.message
    end
  end
end
```

### ミドルウェアの登録

自分で作成したミドルウェアをミドルウェアチェーンの一部に追加する方法は下記の通りです。


```ruby
Sidekiq.configure_client do |config|
  config.client_middleware do |chain|
    chain.add Middleware::Sidekiq::Client::CustomerJobAttribute
  end
end

Sidekiq.configure_server do |config|
  config.server_middleware do |chain|
    chain.add Middleware::Sidekiq::Server::ErrorLogger, :foo => 1, :bar => 2
  end
end
```

SidekiqをRailsと一緒に使っている場合は、上記のコードを `config/initializers/sidekiq.rb` に置くことをおすすめです。

### クライアントミドルウェアはサーバーとクライアントの両方で登録すること

Sidekiqサーバーで実行されているジョブは、それ自体が新しいジョブをSidekiqにプッシュできます。このことから、SidekiqサーバーはSidekiqクライアントも兼ねているということになります。つまり、もしあなたがクライアントミドルウェアを作成したなら、そのミドルウェアは `configure_client` ブロックだけでなく、 `configure_server` ブロックでも設定しなくてはいけません。 

```ruby
Sidekiq.configure_client do |config|
  config.client_middleware do |chain|
    chain.add Middleware::Sidekiq::Client::CustomerJobAttribute
  end
end

Sidekiq.configure_server do |config|
  config.client_middleware do |chain|
    chain.add Middleware::Sidekiq::Client::CustomerJobAttribute
  end
  config.server_middleware do |chain|
    chain.add Middleware::Sidekiq::Server::ErrorLogger, :foo => 1, :bar => 2
  end
end
```

### ミドルウェアを削除する

もしミドルウェアを削除するときは、下記のようにすればOKです。

```ruby
Sidekiq.configure_server do |config|
  config.server_middleware do |chain|
    chain.remove Some::Middleware
  end
end
```

### Sidekiqの起動時にミドルウェアの順番を調査する

`-v` オプションを付けてSidekiqを起動すれば、設定済みのクライアントミドルウェアとサーバーミドルウェアを出力することができます。

注意点が1つあります。`yield` ブロックが途中で実行されなかった場合は、チェーン上の残りのミドルウェアも実行されなくなる点にご注意ください。このことから、ミドルウェアによってはチェーン上での実行順に特に注意を払う必要があります。

### その他

複数のサービス間で1つのRedisインスタンスを共有し、さらに、Sidekiqサーバーでジョブのプッシュを行うとき、意図しないキューのジョブを処理してしまう可能性があることに注意してください。

Sidekiqは1つのRedis セット型を使用してスケジュールされたジョブを処理します。そして、全てのSidekiqサーバーは、設計者の意図とは異なるキューに対してジョブをキューイングする可能性があります。

その場合、現在のRubyコンテキストに存在しないワーカー名がミドルウェアに渡され、存在しないクラス名に対する例外やそれに類するエラーが起きてしまうことがあります。

https://stackoverflow.com/questions/39780767/sidekiq-delete-or-kill-job-inside-a-middleware