Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

自己紹介 自己紹介

Slide 3

Slide 3 text

@ryosuke_sato

Slide 4

Slide 4 text

サトウリョウスケ サトウリョウスケ 株式会社フィードフォース ソーシャルPLUS バックエンドエンジニア 開発リーダー 主な関心領域 Ruby, Rails, TypeScript, OAuth2, Open ID Connect, Micro Services, Serverless, k8s and so on.

Slide 5

Slide 5 text

バックエンドエンジニア積極採用中 バックエンドエンジニア積極採用中 ご興味あれば気軽にお声掛け下さい

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

今日のお話 今日のお話

Slide 8

Slide 8 text

その前に その前に

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

RubocopChallenger という gem を作った .rubocop̲todo.yml を読み込んで auto-correct 可能なルールを修正して、CI から自動で PR を作 ってくれる 約 8 ヶ月で 91 種類の違反コードが修正された ✅ .rubocop̲todo.yml は 1432 行から 473 行に ✨

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

ついに終わりました しかしまだ手動で直す必要のあるルールが 65 個 ある。。 これについてはハードモードを作ろうと考えて いる という訳で今後もぼちぼちやっていきます

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

今日のお話 今日のお話

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

何ができるの? 何ができるの?

Slide 18

Slide 18 text

WEB API CLIENT を作るためのフ WEB API CLIENT を作るためのフ レームワーク レームワーク (を目指しています)

Slide 19

Slide 19 text

WEB API CLIENT ? WEB API CLIENT ?

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

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 ↩

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

MY API CLIENT ? MY API CLIENT ?

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

注意 注意 現在 v0.5.1 でベータ版 一応最低限必要な機能は使える けど Issue も結構ある。。 欲しい機能が揃った & 安定したら v1.0.0 にする 予定

Slide 26

Slide 26 text

簡単に実装できる 簡単に実装できる

Slide 27

Slide 27 text

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') # => #

Slide 28

Slide 28 text

GENERATOR あります GENERATOR あります

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

エラーハンドリングを便利に扱 エラーハンドリングを便利に扱 えるようにする えるようにする

Slide 31

Slide 31 text

エラーハンドリングは意外に面倒 エラーハンドリングは意外に面倒 レスポンスが空の場合は JSON Parse に失敗する JSON を期待していたら XML が返ってきたり 一部のエラーだけエラーコードが存在しないケ ース 優先順位を決めて柔軟に判断が必要になったり ▶ my_api_client なら簡単

Slide 32

Slide 32 text

基本的な使い方 基本的な使い方 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

Slide 33

Slide 33 text

応用編 応用編

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

ステータスが 5xx だった時に block を実行する error_handling status_code: 500..599 do |params, logger| logger.warn 'Server error occurred.' raise MyApiClient::ServerError, params end

Slide 36

Slide 36 text

レスポンス 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

Slide 37

Slide 37 text

組み合わせ OK 。デフォルトは MyApiClient::Error が raise される error_handling status: 400, json: { '$.errors.code': 20..29 }

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

使用例 使用例 api_clinet = ExampleApiClient.new begin api_clinet.get_users rescue MyApiClient::Error => e # Error handling end

Slide 40

Slide 40 text

TIPS TIPS 後に書いた error̲handling が優先されます クラスを継承している場合は子クラスの error̲handling が優先されます Rails の rescue̲from と似てる

Slide 41

Slide 41 text

リトライ機能もあります リトライ機能もあります

Slide 42

Slide 42 text

任意の例外に対してリトライを定義できる ActiveJob の retry̲on とほぼ同じ ほぼ MyApiClient::NetworkError のために作っ た機能 ネットワークが瞬断した時とかに便利 同期処理でのリトライなので、後続の処理に影 響が出ないかどうかに注意 class ExampleApiClient < MyApiClient::Base endpoint 'https://example.com' retry_on MyApiClient::NetworkError, wait: 0.1.seconds, attem end

Slide 43

Slide 43 text

こういう使い方もできるかも # 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

Slide 44

Slide 44 text

BUGSNAG との親和性 BUGSNAG との親和性

Slide 45

Slide 45 text

WEB API を使っていると、予期せぬエ WEB API を使っていると、予期せぬエ ラーが返って来る事がある ラーが返って来る事がある API のドキュメントに書いてなかった 500 Internal Server Error こちらが想定していなかったレアケース

Slide 46

Slide 46 text

エラーログを収集して正しく対応する エラーログを収集して正しく対応する 必要がある 必要がある まず原因が server 側か client 側なのかを知りたい そういう時に MyApiClient で正しく error̲handling を定義して おけば調査が簡単になる Bugsnag が便利

Slide 47

Slide 47 text

MyApiClient::Error#metadata を使うとエラー情 報がわかりやすい begin api_clinet = ExampleApiClient.new(access_token: 'access_toke api_clinet.get_users #=> # 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|

Slide 48

Slide 48 text

v0.5.0 で を 以下の記述だけでも metadata が Bugsnag に送ら れるようになります Bugsnag.leave̲breadcrumb サポ ートしました begin api_clinet = ExampleApiClient.new(access_token: 'access_toke api_clinet.get_users #=> # rescue MyApiClient::Error => e Bugsnag.notify(e) end

Slide 49

Slide 49 text

No content

Slide 50

Slide 50 text

テストしやすくする テストしやすくする

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

リクスエストパラメータを使ったレスポンスを返 すようにスタブ化したい場合 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

Slide 53

Slide 53 text

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)

Slide 54

Slide 54 text

例外が発生した時のテストを書きたい場合 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

Slide 55

Slide 55 text

今日話せなかった詳細は 今日話せなかった詳細は にあります にあります 日本語 日本語 ドキュメント ドキュメント

Slide 56

Slide 56 text

まとめ まとめ `my_api_client` という Web API Client を作るための フレームワークを作っています 簡単に実装できる エラーハンドリングを便利に扱えるようにする Bugsnag との親和性 テストしやすくする

Slide 57

Slide 57 text

バグの修正含め、鋭意製作中です プロダクトの本番環境でも少しずつ移行中です 興味ある方は是非触ってみて下さい

Slide 58

Slide 58 text

THANK YOU! THANK YOU!