\非公式/ Go Conference 2022 Spring スポンサー企業4社 アフタートーク 2022年4月28日
© 2022 LayerX Inc.1Go API クライアントの実装〜Go Conference に載せれなかったTIPS〜\非公式/ Go Conference 2022 Springスポンサー企業4社 アフタートーク2022.04.28
View Slide
© 2022 LayerX Inc.2自己紹介株式会社LayerX中川佳希@yyoshiki41バクラク請求書のテックリードバックエンドからフロントエンドまで.SaaS が扱う業務ドメインへの好奇心とサービスの成長に日々ワクワクを感じています.
© 2022 LayerX Inc.3最近のイベント4月20日(水)SaaS.tech #2マルチテナントのアプリケーション実装 〜実践編〜4月23日(土)Go Conference 2022 SpringGo API クライアントの実装4月28日(木)今日\非公式/ Go Conference 2022 Spring スポンサー企業4社 アフタートーク乗り切ればGW \(^o^)/
© 2022 LayerX Inc.4今日の発表4月23日(土)Go Conference 2022 SpringGo API クライアントの実装1. 認証2. APIコールの実装3. エラーハンドリング4. テスト今日は、登壇時に載せきれなかったテクニックを紹介していきます。
© 2022 LayerX Inc.51. Logger2. Functional Option Pattern3. デバッグ4. アンチパターンa. initb. DELETE メソッドAgenda
© 2022 LayerX Inc.6Logger インターフェイスを定義しておくアプリ側での拡張したロギング設定が使える// Logger generic interface for loggertype Logger interface {Printf(string, ...interface{})}// 複数の Severity を持たせる場合type LeveledLogger interface {Info(string, ...interface{})Warn(string, ...interface{})Error(string, ...interface{})}Logger// Config is a setting for 3rd Party APIs.type Config struct {…Log Logger}func (c *Config) SetLogger(l Logger) {c.Log = l}
© 2022 LayerX Inc.7ライブラリ側でのログLoggerresponse, err := httpClient.Do(req)if err != nil {…}defer response.Body.Close()c.logf("[3rd Party API] %s: %v %v", response.Status, req.Method, req.URL.String())
© 2022 LayerX Inc.8複数のパラメータがあり, 全てが必須な引数でない場合などに有効.ボディパラメータのオブジェクトに, キーなどが自由に設定できるAPI を想定.例Functional Option{"email": "[email protected]","custom_flag": true,"custom_key": "value"}
© 2022 LayerX Inc.9Functional Optiontype Properties map[string]interface{}type Prop func(*Properties)func PropEmail(v interface{}) Prop {return func(args *Properties) {(map[string]interface{})(*args)["email"] = v}}func PropCustom(k string, v interface{}) Prop {return func(args *Properties) {(map[string]interface{})(*args)[k] = v}}
© 2022 LayerX Inc.10アプリからの呼び出しctx := context.Background()resp, err := client.Create(ctx,api.PropEmail("[email protected]"),api.PropCustom("custom_flag", true),api.PropCustom("custom_key", "value"),)if err != nil {log.Fatal(err)}Functional Option
© 2022 LayerX Inc.11httputil.DumpResponse受け取ったHTTPレスポンスを出力するデバッグ関数HTTPステータスコード, ホスト名, ヘッダー情報, ボディパラメータなどが出力されるデバッグfunc DumpResponse(resp *http.Response, body bool) ([]byte, error)
© 2022 LayerX Inc.12httputil.DumpResponseデバッグresp, err := http.Get(ts.URL)if err != nil {log.Fatal(err)}defer resp.Body.Close()dump, err := httputil.DumpResponse(resp, true)if err != nil {log.Fatal(err)}fmt.Printf("%q", dump)
© 2022 LayerX Inc.13httputil.DumpRequestOut実行したリクエストのパラメータを出力するデバッグ関数HTTPメソッド, ホスト名, ヘッダー情報, ボディパラメータなどが出力されるデバッグfunc DumpRequestOut(req *http.Request, body bool) ([]byte, error)
© 2022 LayerX Inc.14httputil.DumpRequestOutデバッグconst body = "Go is a general-purpose language designed with systems programming in mind."req, err := http.NewRequest("PUT", "http://www.example.org", strings.NewReader(body))if err != nil {log.Fatal(err)}dump, err := httputil.DumpRequestOut(req, true)if err != nil {log.Fatal(err)}fmt.Printf("%q", dump)
© 2022 LayerX Inc.15httputil.DumpRequestOut vs httputil.DumpRequest> DumpRequestOut is like DumpRequest but for outgoing client requests.httputil.DumpRequestOut=> Outgoing Request=> クライアント側で, 送信するリクエストのデバッグ用httputil.DumpRequest=> サーバー側で, 受け取ったリクエストのデバッグ用デバッグ
© 2022 LayerX Inc.16ライブラリの init 関数実行環境や外部からのインジェクションなどへの対応が難しくなる.アプリからみると, 暗黙的な関数実行が行われる.アンチパターンtype Config struct {...}var _config Configfunc init() {_config = Config{}}
© 2022 LayerX Inc.17ライブラリの init 関数init 関数は避けて, 関数化を考える.アンチパターンfunc NewConfig(args …) Config {return Config{…}}func NewDefaultConfig() Config {return NewConfig(...)}
© 2022 LayerX Inc.18DELETE メソッド(Go に限った話ではありませんが)DELETE メソッドのAPI エンドポイントへは基本的にボディペイロードを投げない.※ 仮にボディペイロードが要求されているなら, Content-Length を忘れずに!(サーバー側でボディの読み出しが行えない)ちなみに, POST/PUT は Content-Length ヘッダー必須. DELETE は任意..c.f. RFC7231 の DELETE セクションA payload within a DELETE request message has no defined semantics;sending a payload body on a DELETE request might cause some existingimplementations to reject the request.アンチパターン
© 2022 LayerX Inc.19DELETE メソッド実際にハマった例nil (ボディペイロードなし) でリクエストを送ったつもりだったが,httputil.DumpRequestOut でログを吐いてみると..アンチパターンvar body io.ReaderjsonParams, err := json.Marshal(nil)if err != nil {return err}body = bytes.NewBuffer(jsonParams)req, err := http.NewRequest(http.MethodDelete, ts.URL, body)
© 2022 LayerX Inc.20DELETE メソッドnull として, ボディペイロードを送ってしまっていた.そのため, API 側からは BadRequest が返された.(DELETE でのボディペイロードを受け付けない)アンチパターンDELETE / HTTP/1.1Host: 127.0.0.1:63318User-Agent: Go-http-client/1.1Content-Length: 4Accept-Encoding: gzipnull
© 2022 LayerX Inc.21DELETE メソッド対処法:nil チェックなどで, ボディペイロードなしになるようにチェックアンチパターンvar body io.Readerif postPayload != nil {jsonParams, err := json.Marshal(postPayload)if err != nil {return}body = bytes.NewBuffer(jsonParams)}req, err := http.NewRequest(http.MethodDelete, ts.URL, body)
© 2022 LayerX Inc.22DELETE メソッド対処法:nil チェックなどで, ボディペイロードなしになるようにチェックその後、httputil.DumpRequestOut でログを取る.意図した Outgoing Request に \(^o^)/アンチパターンDELETE / HTTP/1.1Host: 127.0.0.1:63420Accept-Encoding: gzipUser-Agent: Go-http-client/1.1dump, err := httputil.DumpRequestOut(req, true)
© 2022 LayerX Inc.23Have a Nice GW \(^o^)/まとめ