MyApiClient

7c4ff97e74b3f52858796fd0cc358fb5?s=47 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

7c4ff97e74b3f52858796fd0cc358fb5?s=128

Ryz310

June 21, 2019
Tweet

Transcript

  1. MY API CLIENT MY API CLIENT 銀座Rails #10 2019/06/21

  2. 自己紹介 自己紹介

  3. @ryosuke_sato

  4. サトウリョウスケ サトウリョウスケ 株式会社フィードフォース ソーシャルPLUS バックエンドエンジニア 開発リーダー 主な関心領域 Ruby, Rails, TypeScript,

    OAuth2, Open ID Connect, Micro Services, Serverless, k8s and so on.
  5. バックエンドエンジニア積極採用中 バックエンドエンジニア積極採用中 ご興味あれば気軽にお声掛け下さい

  6. None
  7. 今日のお話 今日のお話

  8. その前に その前に

  9. 来てく 来てく れた人向けにその後の話を少し れた人向けにその後の話を少し だけ だけ 前回 ( 銀座 RAILS#7,

    3/26) 前回 ( 銀座 RAILS#7, 3/26)
  10. RubocopChallenger という gem を作った .rubocop̲todo.yml を読み込んで auto-correct 可能なルールを修正して、CI から自動で PR

    を作 ってくれる 約 8 ヶ月で 91 種類の違反コードが修正された ✅ .rubocop̲todo.yml は 1432 行から 473 行に ✨
  11. None
  12. ついに終わりました しかしまだ手動で直す必要のあるルールが 65 個 ある。。 これについてはハードモードを作ろうと考えて いる という訳で今後もぼちぼちやっていきます

  13. 前回の発表は Twitter から動画が見られます

  14. None
  15. 今日のお話 今日のお話

  16. my̲api̲client っていう gem を作っています

  17. 何ができるの? 何ができるの?

  18. WEB API CLIENT を作るためのフ WEB API CLIENT を作るためのフ レームワーク レームワーク

    (を目指しています)
  19. WEB API CLIENT ? WEB API CLIENT ?

  20. None
  21. 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 ↩
  22. WEB API CLIENT で困るケース WEB API CLIENT で困るケース API の実装に追い付いていない

    サードパーティ製だと特に ファーストパーティ製でもたまに追い付いて いなかったり そもそも API Client が提供されていない SDK for Ruby がいつもあるとは限らない ▶ 自分で作らざるを得ないシーンが結構ある
  23. MY API CLIENT ? MY API CLIENT ?

  24. コンセプト コンセプト Web API Client を作るためのフレームワーク 簡単に実装できる エラーハンドリングを便利に扱えるようにする Bugsnag との親和性

    テストしやすくする
  25. 注意 注意 現在 v0.5.1 でベータ版 一応最低限必要な機能は使える けど Issue も結構ある。。 欲しい機能が揃った

    & 安定したら v1.0.0 にする 予定
  26. 簡単に実装できる 簡単に実装できる

  27. 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>
  28. GENERATOR あります GENERATOR あります

  29. 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
  30. エラーハンドリングを便利に扱 エラーハンドリングを便利に扱 えるようにする えるようにする

  31. エラーハンドリングは意外に面倒 エラーハンドリングは意外に面倒 レスポンスが空の場合は JSON Parse に失敗する JSON を期待していたら XML が返ってきたり

    一部のエラーだけエラーコードが存在しないケ ース 優先順位を決めて柔軟に判断が必要になったり ▶ my_api_client なら簡単
  32. 基本的な使い方 基本的な使い方 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
  33. 応用編 応用編

  34. ステータスが 4xx だった時に例外を raise する error_handling status_code: 400..499, raise: MyApiClient::Clie

  35. ステータスが 5xx だった時に block を実行する error_handling status_code: 500..599 do |params,

    logger| logger.warn 'Server error occurred.' raise MyApiClient::ServerError, params end
  36. レスポンス 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
  37. 組み合わせ OK 。デフォルトは MyApiClient::Error が raise される error_handling status: 400,

    json: { '$.errors.code': 20..29 }
  38. 正規表現も使える error_handling json: { '$.errors.message': /something went wro

  39. 使用例 使用例 api_clinet = ExampleApiClient.new begin api_clinet.get_users rescue MyApiClient::Error =>

    e # Error handling end
  40. TIPS TIPS 後に書いた error̲handling が優先されます クラスを継承している場合は子クラスの error̲handling が優先されます Rails の

    rescue̲from と似てる
  41. リトライ機能もあります リトライ機能もあります

  42. 任意の例外に対してリトライを定義できる ActiveJob の retry̲on とほぼ同じ ほぼ MyApiClient::NetworkError のために作っ た機能 ネットワークが瞬断した時とかに便利

    同期処理でのリトライなので、後続の処理に影 響が出ないかどうかに注意 class ExampleApiClient < MyApiClient::Base endpoint 'https://example.com' retry_on MyApiClient::NetworkError, wait: 0.1.seconds, attem end
  43. こういう使い方もできるかも # 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
  44. BUGSNAG との親和性 BUGSNAG との親和性

  45. WEB API を使っていると、予期せぬエ WEB API を使っていると、予期せぬエ ラーが返って来る事がある ラーが返って来る事がある API のドキュメントに書いてなかった

    500 Internal Server Error こちらが想定していなかったレアケース
  46. エラーログを収集して正しく対応する エラーログを収集して正しく対応する 必要がある 必要がある まず原因が server 側か client 側なのかを知りたい そういう時に

    MyApiClient で正しく error̲handling を定義して おけば調査が簡単になる Bugsnag が便利
  47. 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|
  48. 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
  49. None
  50. テストしやすくする テストしやすくする

  51. 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
  52. リクスエストパラメータを使ったレスポンスを返 すようにスタブ化したい場合 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
  53. 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)
  54. 例外が発生した時のテストを書きたい場合 def execute_api_request ExampleApiClient.new.request(user_id: 1) end my_api_client_stub(ExampleApiClient, :request, raise: MyApiCli

    expect { execute_api_request }.to raise_error(MyApiClient::Err
  55. 今日話せなかった詳細は 今日話せなかった詳細は にあります にあります 日本語 日本語 ドキュメント ドキュメント

  56. まとめ まとめ `my_api_client` という Web API Client を作るための フレームワークを作っています 簡単に実装できる

    エラーハンドリングを便利に扱えるようにする Bugsnag との親和性 テストしやすくする
  57. バグの修正含め、鋭意製作中です プロダクトの本番環境でも少しずつ移行中です 興味ある方は是非触ってみて下さい

  58. THANK YOU! THANK YOU!