[Rails] Shoryuken + FakeSQSで非同期処理

Railsで非同期処理っていったらDelayedJobとかResqueとかSidekiqとかいろいろありますが、
今回は[Shoryuken](https://github.com/phstc/shoryuken)にチャレンジしてみます。

ShoryukenはAWS SQSからジョブ取り出すワーカーを簡単に作っちゃおうっていうGemなんですが、
今回は本物のSQSは~~貧乏なので~~使わずに[Fake SQS](https://github.com/iain/fake_sqs)というSQS（っぽいもの）をローカルで立てることができるGemを使ってローカルのみで動くShoryuken Workerを書いちゃいましょう。

#登場人物

- AWS SQS: すごく安くキューが使えるニクいやつ
- AWS-SDK: RubyからAWSを簡単に操作できるニクいやつ
- Fake SQS: SQSっぽいものをローカルに立てられるニクいやつ
- Shoryuken: SQSからジョブを取り出すニクいやつ

#導入

あらかじめ適当なRailsプロジェクトを作っておきます。
いつも通りプロジェクトのGemfileに追記して`bundle install`します。

```ruby:Gemfile
gem 'aws-sdk'
gem 'fake_sqs'
gem 'shoryuken'
```

```sh
$ bundle install
```

#設置
まずshoryuken用の設定ファイルを設置します。
おおむねShoryukenのGitHubリポジトリにあるサンプルを流用したものですが、
今回はFakeSQSを使うので、エンドポイントはそれ用になってます。

```yaml:config/shoryuken.yml

aws:
  access_key_id:     access key id
  secret_access_key: secret access key
  region:            region
  sqs_endpoint:      http://localhost:4568
  receive_message:
    attribute_names:
      - ApproximateReceiveCount
      - SentTimestamp
concurrency: 25
delay: 25
queues:
  - default
```

次に、FakeSQSにジョブを差し込む用のキューを作成するためのスクリプトを書きます。
initializersに置いておきましょう。

```ruby:config/initializers/sqs.rb
sqs_client = Aws::SQS::Client.new(
  endpoint: 'http://localhost:4568',
  secret_access_key: 'secret access key',
  access_key_id: 'access key id',
  region: 'region'
)
sqs_client.create_queue(queue_name: 'default')
```

仕上げに簡単なShoryuken Workerを書きます。

```ruby:app/workers/sqs_worker.rb
class SqsWorker
  include Shoryuken::Worker

  shoryuken_options queue: 'default', auto_delete: true, body_parser: :json

  def perform(sqs_msg, body)
    puts sqs_msg, body
  end
end
```

#実行
必要なファイルの設置は終わったので、実行してみましょう。
まずはFake SQS。

```sh
$ bundle exec fake_sqs
```

次は本命のShoryukenを動かします。
-Cで指定したコンフィグファイル、-RでRailsのコードとapp/workers以下のworkerを読み込んでくれます。

```sh
$ bundle exec shoryuken -R -C config/shoryuken.yml
```

動作確認してみます。
rails cからこんな感じでFake SQSのキューにメッセージを送ってみましょう。

```ruby
sqs_client = Aws::SQS::Client.new(
  endpoint: 'http://localhost:4568',
  secret_access_key: 'secret access key',
  access_key_id: 'access key id',
  region: 'region'
)
queue_url = sqs_client.get_queue_url(queue_name: 'default')['queue_url']
sqs_client.send_message(queue_url: queue_url, message_body: {hoge: :fuga}.to_json)
```

シェルにこんな感じの出力があったら成功です。
踊りましょう。

```sh
#<Shoryuken::Message:0x007f2160e435a8>
{"hoge"=>"fuga"}
```

# perform_asyncする
`Shoryuken::Worker` は `perform_async` メソッドで設定したキューにジョブを入れられます。

```rb
SqsWorker.perform_async(hoge: 'fuga')
```

といった具合です。
イカしてますね。
その他の使い方はこちらに。
https://github.com/phstc/shoryuken/wiki/Sending-a-message

## FakeSQSで動かす
FakeSQSを使っているといろいろと問題があって上手く動かないので `Aws::SQS::Client` を使って直接メッセージを入れていたんですが、此度対処法を調べました。
下にあるエラーが出るのであれば、 `initializers/sqs.rb` 辺りに追記すると恐らく動きます。
どうもFakeSQSの仕様上、 `perform_at` や `perform_in` は動かないみたいですが。
FakeSQSを使わないとき（本物のSQSを使う時とか）には対処コードが読み込まれないように注意してください。

### `Aws::Errors::MissingRegionError` が出るとき
Shoryukenが上手くconfigを読めていないっぽい。
`Shoryuken::EnvironmentLoader.load` に設定を読ませると回避できました。  

```ruby
config_file = Rails.root.join('config', 'shoryuken.yml')
config = YAML.load(ERB.new(IO.read(config_file).result)).deep_symbolize_keys
Shoryuken::EnvironmentLoader.load(config)
```

参考: https://github.com/phstc/shoryuken/issues/68

### `Aws::Errors::ChecksumError` が出るとき
aws-sdk v2.3以降で出るっぽい。
MD5を検証しているメソッドを叩き潰したら回避できました。

```ruby
class Aws::Plugins::SQSMd5s::Handler
  def validate_attributes(*args)
    true
  end
end
```

参考: https://github.com/iain/fake_sqs/issues/40