Upgrade to Pro — share decks privately, control downloads, hide ads and more …

MyApiClient

Ryz310
June 21, 2019

 MyApiClient

銀座Rails#10 (https://ginza-rails.connpass.com/event/133628/) での登壇資料です。

発表で紹介しきれなかった機能については日本語ドキュメントをご参照下さい。
https://github.com/ryz310/my_api_client/blob/master/README.jp.md

Ryz310

June 21, 2019
Tweet

More Decks by Ryz310

Other Decks in Technology

Transcript

  1. RubocopChallenger という gem を作った .rubocop̲todo.yml を読み込んで auto-correct 可能なルールを修正して、CI から自動で PR

    を作 ってくれる 約 8 ヶ月で 91 種類の違反コードが修正された ✅ .rubocop̲todo.yml は 1432 行から 473 行に ✨
  2. WEB API CLIENT の振る舞い WEB API CLIENT の振る舞い Business Logic

    ↪ {Ruby Object} Web API Client ↩ ↪ {HTTP Request} API Server ↩ ↪ {HTTP Response} Web API Client ↩ ↪ {Ruby Object} Business Logic ↩
  3. WEB API CLIENT で困るケース WEB API CLIENT で困るケース API の実装に追い付いていない

    サードパーティ製だと特に ファーストパーティ製でもたまに追い付いて いなかったり そもそも API Client が提供されていない SDK for Ruby がいつもあるとは限らない ▶ 自分で作らざるを得ないシーンが結構ある
  4. class ExampleApiClient < MyApiClient::Base endpoint 'https://example.com/v1' attr_reader :access_token def initialize(access_token:)

    @access_token = access_token end # GET https://example.com/v1/users def get_users get 'users', headers: headers, query: { key: 'value' } end # POST https://example.com/v1/users api_clinet = ExampleApiClient.new(access_token: 'access_token' api_clinet.post_user(name: 'name') # => #<Sawyer::Response>
  5. Ruby on Rails を利用している場合は generator 機 能を利用できます $ rails g

    api_client path/to/resource https://example.com get_ 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
  6. エラーハンドリングは意外に面倒 エラーハンドリングは意外に面倒 レスポンスが空の場合は JSON Parse に失敗する JSON を期待していたら XML が返ってきたり

    一部のエラーだけエラーコードが存在しないケ ース 優先順位を決めて柔軟に判断が必要になったり ▶ my_api_client なら簡単
  7. 基本的な使い方 基本的な使い方 class ExampleApiClient < MyApiClient::Base endpoint 'https://example.com' error_handling status_code:

    400..499, raise: MyApiClient::Cl error_handling status_code: 500..599, raise: MyApiClient::Se # (以下略) end
  8. ステータスが 5xx だった時に block を実行する error_handling status_code: 500..599 do |params,

    logger| logger.warn 'Server error occurred.' raise MyApiClient::ServerError, params end
  9. レスポンス JSON の $.errors.code が 10 だった場合 にメソッドを実行 error_handling json:

    { '$.errors.code': 10 }, with: :my_error_ def my_error_handling(params, logger) logger.warn "Response Body: #{params.response.body.inspect}" raise MyApiClient::ClientError, params end
  10. 任意の例外に対してリトライを定義できる ActiveJob の retry̲on とほぼ同じ ほぼ MyApiClient::NetworkError のために作っ た機能 ネットワークが瞬断した時とかに便利

    同期処理でのリトライなので、後続の処理に影 響が出ないかどうかに注意 class ExampleApiClient < MyApiClient::Base endpoint 'https://example.com' retry_on MyApiClient::NetworkError, wait: 0.1.seconds, attem end
  11. こういう使い方もできるかも # 1. エラーを検知 error_handling json: { '$.errors.code': 10 },

    with: :access_to # 3. アクセストークンが更新された状態でリトライする retry_on AccessTokenExpired, wait: 1.second, attempts: 1 # ( 中略 ) # 2. アクセストークンが期限切れなので更新する def access_token_expired(params, logger) refresh_access_token! # アクセストークンを更新 raise AccessTokenExpired, params end
  12. MyApiClient::Error#metadata を使うとエラー情 報がわかりやすい begin api_clinet = ExampleApiClient.new(access_token: 'access_toke api_clinet.get_users #=>

    #<Sawyer::Response> rescue MyApiClient::Error => e e.metadata # => { # request_line: 'GET https://example.com/users', # request_headers: {...}, # request_query: {...}, # request_body: {...}, # response_status: 400, # response_headers: {...}, # response_body: {...}, # } Bugsnag.notify(e) do |report|
  13. v0.5.0 で を 以下の記述だけでも metadata が Bugsnag に送ら れるようになります Bugsnag.leave̲breadcrumb

    サポ ートしました begin api_clinet = ExampleApiClient.new(access_token: 'access_toke api_clinet.get_users #=> #<Sawyer::Response> rescue MyApiClient::Error => e Bugsnag.notify(e) end
  14. my̲api̲client̲stub というメソッドを提供してい ます class ExampleApiClient < MyApiClient::Base endpoint 'https://example.com' def

    request(user_id:) get "users/#{user_id}" end end my_api_client_stub(ExampleApiClient, :request, response: { id: response = ExampleApiClient.new.request(user_id: 1) response.id # => 12345
  15. リクスエストパラメータを使ったレスポンスを返 すようにスタブ化したい場合 my_api_client_stub(ExampleApiClient, :request) do |params| { id: params[:user_id] }

    end response = ExampleApiClient.new.request(user_id: 1) response.id # => 1 response = ExampleApiClient.new.request(user_id: 2) response.id # => 2
  16. receive や have̲received を使ったテストを書き たい場合 def execute_api_request ExampleApiClient.new.request(user_id: 1) end

    api_clinet = my_api_client_stub(ExampleApiClient, :request) execute_api_request expect(api_client).to have_received(:request).with(user_id: 1)
  17. まとめ まとめ `my_api_client` という Web API Client を作るための フレームワークを作っています 簡単に実装できる

    エラーハンドリングを便利に扱えるようにする Bugsnag との親和性 テストしやすくする