はじめに
こんにちはMAMADAYSバックエンドチームのrymiyamotoです。最近エルデンリングを遊び倒しています。
MAMADAYSではアプリとWebで利用しているAPI(golang)の仕様をドキュメント化するためにSwaggerを利用しています。
導入をしてから3年以上経過したため、APIの開発運用を進める中で出てきた課題点への施策を綴っていこうと思います。
そもそもSwaggerとは?
SwaggerはOpenAPIというRESTful APIの仕様を記述するためのフォーマットを使用したツールで、仕様が文章化されることで開発者や関係者での認識が取りやすくなります。
動作環境
MAMADAYSではSwaggerの利用にあたって以下のツールを使っています。
ツール名 | 用途 | バージョン |
---|---|---|
swag | ドキュメントの自動生成 | v1.8.0 |
echo-swagger | Swagger UIの表示で利用(ドキュメントの可視化) | v1.3.0 |
Swaggerをそのまま使う分にはyamlを表記するだけですが、MAMADAYSではドキュメントを自動生成するための swag
を使っています。
swag
では定義したstructの型に合わせてドキュメントを生成するのでyamlを直接手で変更する必要がなく楽です。
また生成されたドキュメントのままだと視覚的に分かりにくいため、Swagger UIを表示できるように echo-swagger
を利用しています。
以下MAMADAYSので表記に合わせた簡易的な例です。 (goのバージョンは1.17.8です)
package main import ( "net/http" _ "github.com/rymiyamoto/swagger-test/docs" "github.com/labstack/echo/v4" echoSwagger "github.com/swaggo/echo-swagger" ) type ( Response struct { Int64 int64 `json:"int64"` String string `json:"string"` World *Item `json:"world"` } Item struct { Text string `json:"text"` } ) // @title example // @version 1.0 // @license.name rymiyamoto // @BasePath / func main() { e := echo.New() e.GET("/swagger/*", echoSwagger.WrapHandler) e.GET("/", hello) e.Logger.Fatal(e.Start(":1323")) } // hello godoc // @Summary Hello World ! // @ID HelloWorldIndex // @Tags HelloWorld // @Produce json // @Success 200 {object} Response // @Router / [get] func hello(c echo.Context) error { return c.JSON(http.StatusOK, &Response{ Int64: 1, String: "example", World: &Item{ Text: "hello world !", }, }) }
※go.modとgo.sumは省略しています
$ go install github.com/swaggo/swag/cmd/swag@v1.8.0 $ swag init $ go mod tidy $ go run main.go
NULL許容の値を表現する
sql.NullString
や sql.NullInt64
などのNULL値を含むデータをそのまま使うことができないため swaggertype:"XXX"
で対象のキーに表現したい型を定義するかと思います。
しかしこのままだとNULL許容であるかどうかがわかりません。
方法としては2種類あるので紹介します。
descriptionを追加する
対象のキーにコメントとして書くことでdescriptionが追加でき、ここでNULL許容であるかどうかを表現してます。
Hello struct { NullInt64 sql.NullInt64 `json:"null_int64" swaggertype:"integer"` // nullable NullString sql.NullString `json:"null_string" swaggertype:"string"` // nullable }
extensionsで任意の追加情報を付与する(echo-swagger v1.3.0では非対応)
extensions:"x-XXX"
で任意の追加情報を付与することが可能です。
NULL許容を表現するにあたっては extensions:"x-nullable"
で指定することにします。
Add extension info to struct field
Hello struct { NullInt64 sql.NullInt64 `json:"null_int64" swaggertype:"integer" extensions:"x-nullable"` NullString sql.NullString `json:"null_string" swaggertype:"string" extensions:"x-nullable"` }
ただし echo-swagger(v1.3.0)
上では表示できないため、出力されたyamlをSuwagger Editor上で確認する人向けです。
(表示がうまくいかないのは、依存packageである swaggo/files の内部で保持しているファイルが古そうです)
同一リソース名を扱う
同一リポジトリ内でAppやWeb・Dashboard等でAPIを作成している場合、リソース名が重複します。
このとき swag
側で全体のパスを含めた構造体名に変更してくれますが、その表記が長く冗長になってしまいます。
単純に1つしかSwaggerを利用しなければ気にすることはありませんが、表記が長くならないようにそれぞれprefixを足して見通しを良くしています。
// DashboardHoge 内部向けDashboard用 DashboardHoge struct { Text string `json:"text"` StartAT time.Time `json:"start_at"` EndAT time.Time `json:"end_at"` } // Hoge App用 Hoge struct { Text string `json:"text"` }
structでリクエストのbodyを表現する
bodyを扱う場合に各APIのコメントに以下のように記載すればよいです。
// post godoc // @Summary Hello World ! // @ID HelloWorldPost // @Tags HelloWorld // @Produce json // @Param title body string true "タイトル" // @Param description body string false "説明" // @Success 200 {object} Response // @Router / [post] func post(c echo.Context) error { // ... }
しかしこの状態だと
- パラメーターが増えると定義が面倒
- リクエストの定義とコントローラーの定義を別階層で管理していると抜け漏れが発生しやすくなる
- 同一のリクエストを使いまわしていると修正が冗長になってしまう
となってしまいます。
その対策として、bodyには定義しているformのstructを渡すようにしています。
こうすることで、form部分の修正のみで対応するAPIのbodyも一括で変更できるため管理が簡単になります。
// post godoc // @Summary Hello World ! // @ID HelloWorldPost // @Tags HelloWorld // @Produce json // @Param body body Form true "request body" // @Success 200 {object} Response // @Router / [post] func post(c echo.Context) error { // ... } Form struct { Title string `json:"title"` // require Description string `json:"description"` // option }
終わりに
swag
を使い続けて3年も経過すると色々と気になるところが出てくるので、利用ルールの制定多くなってきました。
理想を言えばその部分も定義できればより汎用性が出そうです。
しかし適期的にアップデート内容を確認してきましたが、少しずつOpen API 3.0の記法も使えるようになってきているのでしばらくは使っていこうと思います。
皆様良きSwaggerライフを!