ApartmentとDelayedJobでマルチテナントJob


# 公式では非推奨

> Delayed::Job
> Has been removed... See apartment-sidekiq for a better backgrounding experience

公式では上記のようにDelayedJob対応は削除され、sidekiqを推奨していますが
諸事情によりDelayedJobを採用した際のモンキーパッチ対応です

## sidekiqでDBConnectionPool系エラー
複数のテナントのJOBを同時に実行すると発生
リトライ時や逐次実行など、他テナントJOBが動いてなければ成功するので
sidekiqはマルチスレッドだがテナント切替のestablish_connectionが
スレッドセーフではないことが原因か？

# 環境
Ruby v2.2.2
Rails v4.2.3
gem [apartment](https://github.com/influitive/apartment) v1.0.2
gem [delayed_job_active_record](https://github.com/collectiveidea/delayed_job_active_record) v4.0.3

# Apartment

テナント毎のDBにjobデータが分散するとDelayedJobが取得できないので
デフォルトDBに集約する設定を追加する

```rb:config/initializers/apartment.rb
Apartment.configure do |config|
  config.excluded_models = %w{ Delayed::Backend::ActiveRecord::Job }
end

```

# DelayedJob

全テナントのjobデータが入るため、区別のためテナントカラムを増やす

```rb:db/migrate/***_create_delayed_jobs.rb
class CreateDelayedJobs < ActiveRecord::Migration
  def self.up
    create_table :delayed_jobs, force: true do |t|
      t.string :tenant,                null: false # マルチテナント対応による拡張
      # (略)delayed_jobの各カラム
    end
  end
end

```

# 新実装方法

旧実装と考え方は変わっていないが
テナント情報の保存とJob実行時に接続を切り替える処理をそれぞれ拡張

```ruby:config/initializers/delayed_job.rb
Delayed::Worker.prepend(Delayed::Worker::Multitenancy)
Delayed::Backend::ActiveRecord::Job.prepend(Delayed::Backend::ActiveRecord::Multitenancy)
```

実際のRun処理のみを拡張する

```ruby:lib/autoload/delayed/worker/multitenancy.rb
module Delayed
  class Worker
    module Multitenancy
      def run(job)
        in_tenant(job.tenant) do
          super
        end
      end

      private

      def in_tenant(tenant)
        Apartment::Tenant.switch(tenant) do
          yield
        end
      end
    end
  end
end
```

分割しただけで旧実装と変わらず

```ruby:lib/autoload/delayed/backend/active_record/multitenancy.rb
module Delayed
  module Backend
    module ActiveRecord
      module Multitenancy
        def self.prepended(mod)
          mod.class_eval do
            after_initialize :set_tenant, if: :new_record?
          end
        end

        private

        def set_tenant
          self.tenant = Apartment::Tenant.current
        end
      end
    end
  end
end
```

## 結果

無事に [text_after_commit](https://github.com/grosser/test_after_commit) を入れたrspecで成功

# 旧拡張方法（非推奨・参考記録）
テナント情報の保存とJob実行時に接続を切り替える処理を拡張

```rb:config/initializers/delayed_job.rb
class Delayed::Backend::ActiveRecord::Job
  after_initialize :bind_tenant, if: :new_record?

  # Jobオブジェクト生成
  def payload_object_with_tenant
    tenanted_job do
      payload_object_without_tenant
    end
  end
  alias_method_chain :payload_object, :tenant

  # Job実行
  def invoke_job_with_tenant
    tenanted_job do
      invoke_job_without_tenant
    end
  end
  alias_method_chain :invoke_job, :tenant

  private

  def tenanted_job(&block)
    result = nil
    Apartment::Tenant.switch(self.tenant) do
      result = block.call
    end
    result
  end

  def bind_tenant
    self.tenant = Apartment::Tenant.current
  end
end
```

## 結果

job取得やjob実行で毎回コネクションを切り替えるため効率は良くない
gem [text_after_commit](https://github.com/grosser/test_after_commit) を導入してrspecのafter_commitでjob化せずに実行するとエラー
実際の動作は問題無さそうだが[text_after_commit](https://github.com/grosser/test_after_commit)はRails5で取り込まれるので要改善

# 参考

* [Apartment でマルチテナントサービスを作成する](http://qiita.com/kakipo/items/a584d24771dff019d3a9)
* [【Rails 4】delayed_jobを使う](http://qiita.com/azusanakano/items/1d2629763f35b5466286)