The Gopher character is based on the Go mascot designed by Renée French.
概要
TIG DXチーム所属の多賀です。最近はフロントのコードを書いたりすることも増えましたが、引き続き Go も触っています。
Go で OpenAPI(Swagger) からコード生成する際には、 go-swagger をよく利用しています。
go-swagger については他記事でもまとめられています。
ただ、 go-swagger は Swagger 2.0 にのみ対応しており、OpenAPI 3.0 系が使えない問題がありました。最新に追従していく上でも Open API 3.0 系に寄せていきたいと考えていたので、なにか使えるツールはないか探したところ、以下を見つけました。
https://github.com/deepmap/oapi-codegen
使えるかどうか実際に動かして試してみます。
ざっと見た感じは、以下の模様です。
- Open API 3.0 の定義から Go のソースコードを生成できる
- echo, chi の形式でServerソースが出力できる
- Go の interface で Open API の仕様が定義され interfaceを満たすように実装していく
調査
実際に OpenAPI 定義からコードを出力してみます。
ライブラリ側で OpenAPI定義のサンプルが用意されていたためそのまま利用してみます。
https://github.com/deepmap/oapi-codegen/blob/master/examples/petstore-expanded/petstore-expanded.yaml
ざっくり以下のAPI が定義されています。
GET /pets |
上記を openapi.yml
としてダウンロードしました。
とりあえず、コード生成を実行してみます。
# コマンドインストール |
こちらで Goのコードが1ファイルに生成されました。
生成項目としては以下4点です。
- 型定義
- http client
- http server
- OpenAPI spec
実際に利用する際は、必要な分だけ生成・管理したいかなと思います。
生成コードとはいえ、1ファイルにまとまっていると少々読みづらかったりもします。
コマンドのパラメータで制御できるようでしたので、それぞれ別にコード生成し中身を確認していきます。
(生成コードは長くなるため一部抜粋しています。)
型定義
- OpenAPI の
components
から struct を生成 - リクエスト Bodyの定義も同様に生成
コマンド
oapi-codegen -generate "types" -package openapi openapi.yml > ./openapi/types.gen.go |
生成コード
// NewPet defines model for NewPet. |
http client
- API仕様が interface として出力
- 2種類の interface が定義
- ClientInterface
- API実行の結果 http.Response が返却される
- ClientWithResponsesInterface
- API実行の結果の Response Body を parse して struct へ詰めてくれる
- Body を []byte 形式で保持するためメモリ効率はいまいち
- API実行の結果の Response Body を parse して struct へ詰めてくれる
- ClientInterface
- 上記 interface を実装した struct も合わせて生成済
- 生成された Client を利用するだけで良い
コマンド
oapi-codegen -generate "client" -package openapi openapi.yml > ./openapi/client.gen.go |
生成コード
// The interface specification for the client above. |
利用コード
c := openapi.NewClient("http://localhost:8888") |
http server
- API仕様が interface として定義
- interface を実装する形で Server側のコードを実装していく
コマンド
oapi-codegen -generate "server" -package openapi openapi.yml > ./openapi/server.gen.go |
生成コード
// ServerInterface represents all server handlers. |
利用コード
// ServerInterface を実装するような struct を定義 |
chi 形式でも出力できます。
|
OpenAPI spec
- base64形式で
openapi.yaml
を保持
コマンド
oapi-codegen -generate "spec" -package openapi openapi.yml > ./openapi/spec.gen.go |
生成コード
|
レビュー
良さそうな点と気になる点をまとめました。
良さそうなところ
- 生成コードが薄めで良い
- go-swagger は生成コードが重厚かつintefaceで分離されて実装が追いづらい点が気になっていた
- echo/chi の APIが直接触れる
- echoやchi などの選択も結構好み
- tag指定して出力すると依存のある定義のみが出力される
oapi-codegen -include-tags pet -generate "server" openapi.yml
- クエリパラメータが struct へ Bindされる
- パラメータのバリデーションに対応
- デフォルトだとリクエストボディはバリデーションされない (読まれないため)
- Echo だと middleware をいれれば Body のバリデーションエラーも見れる
middleware.OapiRequestValidator(swagger)
- OpenAPI の spec が必要
気になるところ
- 拡張タグは動かなそう
x-XXX
系は動作しない
- 生成 struct の型定義に違和感
- required が 基本型 で optional が pointer 型
- Go のコードでよく見る定義と逆なので注意が必要
- レスポンス定義は Bind されない
- 実装者がレスポンスの struct を間違えないようにする必要がある
- (個人的には一番いまいちかなと感じた点です。生成コード上仕方なさそうでしたが..)
- 1 interface で Open API の定義が出力される
- include-tags を利用してタグ別に出力はうまく動作しない
- Server interface の実装が1つでないといけないため (echo/chiに登録できない)
- 特定の tag のみ実装するケースでの利用可能
- include-tags を利用してタグ別に出力はうまく動作しない
- 同一 package に押し込める必要あり
- server, client コードは types に依存している
- echo と chi だと若干 echo 側のほうがリクエストの Bind が良い
- echo だと生成 Handler の引数にリクエストパラメータの struct が定義される
- chi だと context から取得する必要あり
- 生成コードで ctx から取り出すヘルパー関数あり
利用するとしたら..?
- echoでの出力を選択
- リクエストパラメータのバインドがしっかりされるため
- middleware 利用だがリクエストボディのバリデーションチェックもできて良い
- 出力は同一パッケージでファイルを分けて管理
- サーバー
- server, types, spec
- クライアント
- client, types
- サーバー
- 生成コード用の パッケージ (ディレクトリ) を切る
- 各生成コードに依存があるため
- 各API のレスポンス定義の命名を統一する
- レスポンス Body の Bindがされないため
${operationId}Res
or${operationId}Response
所感
ざっとコード生成を試して、コード側の確認をしてみました。
結構利用できそうだなというのが全体的な感想で、OpenAPI3.0系の制約がある場合は、oapi-codegen を実際に利用してみたいです。