逃げる8回で会心の一撃

Web エンジニアのサトウリョウスケが開発とか色々書くブログです

my_api_client v0.16.0 をリリースしました🚀

前回のリリースから 1 週間ほどですが、今日予定していたライブがコロナウイルスの影響で中止になったので暇を持て余しました 😷

github.com

v0.16.0 の新機能

2 つありますが、どちらも若干の Breaking Change です。 とはいえ普通に使っていたら全く影響を受けないと思います。

新機能 1. エラーハンドラがエラーを検出した際は常に例外を raise するようになりました

my_api_client では JSON API からのレスポンス内容に応じて例外を発生させる error_handling というメソッドが利用できます。

以下に error_handling を利用した例を示します。

class ExampleApiClient < ApplicationApiClient
  endpoint 'https://example.com'

  error_handling json: { '$.errors.code': 10 }

  error_handling json: { '$.errors.code': 20 }, raise: MyErrorClass

  error_handling json: { '$.errors.code': 30 }, with: :my_error_handling

  error_handling json: { '$.errors.code': 40 } do |params, logger|
    # Do something.
  end

  # GET error/:code
  def request
    get 'path/to/resouce'
  end

  private

  def my_error_handling(params, logger)
    # Do something.
  end
end

この例の場合、 ExampleApiClient#request を実行すると GET https://example.com/path/to/resouce に対してリクエストが実行され、レスポンスボディが JSON 形式だった場合、JSONPath $.error.code の値に応じて以下の処理を実行します。

  • 10 だった場合 MyApiClient::Error を発生させる
  • 20 だった場合 MyErrorClass を発生させる
  • 30 だった場合 #my_error_handling を実行する (例外は発生しない)
  • 40 だった場合 do ~ end を実行する (例外は発生しない)

MyApiClient::Errorraise オプションで例外クラスを指定しなかった場合のデフォルトの例外クラスです。

この時、従来は 3040 のように withblock を利用した場合は、処理の中で明示的に raise を実行しない限り、例外は発生しませんでした。 エラー検出時に例外を発生させるかどうかは、 my_api_client の利用者に委ねられていた形になります。

しかしながら、ここに自由度を持たせるよりも、 エラー検出時には必ず raise させて rescue で異常時の処理を記述する 、という方式に統一した方が my_api_client の利用方法としても理解しやすく、特に困るケースも想定されなかったことから、以下のように変更することにしました。

  • 10 だった場合 MyApiClient::Error を発生させる (変更なし)
  • 20 だった場合 MyErrorClass を発生させる (変更なし)
  • 30 だった場合 #my_error_handling を実行し、 MyApiClient::Error を発生させる
  • 40 だった場合 do ~ end を実行し、 MyApiClient::Error を発生させる

今後はエラー検出時には常に何らかの例外が raise されるようになります。 上記の例では MyApiClient::Error が発生しますが、 withblock と同時に raise を指定すれば、任意の例外クラスが発生するようになります。

これにより、 withblock は例外の前処理という位置付けになります。ユースケースとしてはログ出力や slack への通知などが考えられます。

新機能 2. 標準のエラーハンドラが用意されました

my_api_client では generator 機能 が用意されており、 $ rails g api_client path/to/resource get:path/to/resource を実行すると以下のファイルが作成されます。

create  app/api_clients/application_api_client.rb
create  app/api_clients/path/to/resource_api_client.rb
invoke  rspec
create    spec/api_clients/path/to/resource_api_client_spec.rb` 

この時、 application_api_client.rb に標準のエラーハンドラの例がいくつか記載されるのですが、例というより必須のエラーハンドラだよね、ということで、 my_api_client の内部で標準実装するようにしました。 これにより、ステータスコード 4xx と 5xx のレスポンスに対しては標準で例外が発生するようなります。また、ネットワーク系のエラーに対しても標準で 300 msec 間隔を空けて 3 回リトライが試行されるようになります。(リトライ処理も従来は明示的な定義が必須でした)

# 従来の `application_api_client.rb` に出力されていた標準のエラーハンドラ例
error_handling status_code: 400..499, raise: MyApiClient::ClientError
error_handling status_code: 500..599, raise: MyApiClient::ServerError

# 従来の `application_api_client.rb` に出力されていた標準のリトライ処理例
retry_on MyApiClient::NetworkError, wait: 5.seconds, attempts: 3

標準で定義されているエラーハンドラは my_api_client/default_error_handlers.rb から参照できます。

error_handling は後から定義した物が優先されますので、例えばステータスコード 400 に対しては独自の例外クラスを発生させるようにしたい場合、継承先のクラスで error_handling status_code: 400, raise: MyErrorClass のように定義すれば、 MyErrorClass が例外として発生するようになります。

所感

社内のプロダクト用に作った gem ですが、少しずつ自分以外のエンジニアも利用してくれるようになってきました。 一方で、自由度が高過ぎると熟知していないと使えない機能が増えてしまう点を課題感として感じるようになってきました。

なるべく自由度の高い gem を意識しつつ、標準の状態でも高度な機能の恩恵を受けられる状態を目指していきたいと思います。

恐らく次の新機能は async/await っぽい機能、または sawyer gem の依存からの脱却なると思います。