OpenAPIからGo言語のコードを自動生成してくれるツールを試してみた
はじめに
Go言語のフレームワークであるEchoを用いてAPIサーバーを開発することになり、OpenAPIからEchoのコードを自動生成するツールを試してみたので、まとめてみました。
今回はOpenAPI Generatorとoapi-codegenの2つのコードジェネレーターツールを試しました。
下準備
まずはOpenAPI定義のyamlファイルを用意します。
サンプルが用意されていたのでそのまま使うことにしました。
APIの定義はこんな感じです。
https://github.com/OAI/OpenAPI-Specification/blob/main/examples/v3.0/petstore.yaml
OpenAPI Generator
まずはOpenAPI Generatorを試してみます。
インストール
Macの場合
brew install openapi-generator
コード生成
openapi-generator generate -i petstore.yaml -g go-echo-server -o ./gen
genereateの各オプションは
-i : 定義ファイルの指定
-g : 生成するclientやserverの指定 ※指定できるもののリストはこちら
-o : 出力先の指定
genディレクトリ配下にファイルやディレクトリが生成されます
.
├── gen
│ ├── Dockerfile
│ ├── README.md
│ ├── go.mod
│ ├── handlers
│ │ ├── api_pets.go
│ │ └── container.go
│ ├── main.go
│ └── models
│ ├── hello-world.go
│ ├── model_error.go
│ └── model_pet.go
└── petstore.yaml
go.mod
module github.com/GIT_USER_ID/GIT_REPO_ID
go 1.16
require github.com/labstack/echo/v4 v4.2.0
main.go
package main
import (
"github.com/GIT_USER_ID/GIT_REPO_ID/handlers"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
e := echo.New()
//todo: handle the error!
c, _ := handlers.NewContainer()
// Middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())
// CreatePets - Create a pet
e.POST("/v1/pets", c.CreatePets)
// ListPets - List all pets
e.GET("/v1/pets", c.ListPets)
// ShowPetById - Info for a specific pet
e.GET("/v1/pets/:petId", c.ShowPetById)
// Start server
e.Logger.Fatal(e.Start(":8080"))
}
handlers/api_pets.go
package handlers
import (
"github.com/GIT_USER_ID/GIT_REPO_ID/models"
"github.com/labstack/echo/v4"
"net/http"
)
// CreatePets - Create a pet
func (c *Container) CreatePets(ctx echo.Context) error {
return ctx.JSON(http.StatusOK, models.HelloWorld {
Message: "Hello World",
})
}
// ListPets - List all pets
func (c *Container) ListPets(ctx echo.Context) error {
return ctx.JSON(http.StatusOK, models.HelloWorld {
Message: "Hello World",
})
}
// ShowPetById - Info for a specific pet
func (c *Container) ShowPetById(ctx echo.Context) error {
return ctx.JSON(http.StatusOK, models.HelloWorld {
Message: "Hello World",
})
}
main.go実行
それでは実際に試してみます。
cd gen
go run main.go
____ __
/ __/___/ / ___
/ _// __/ _ \/ _ \
/___/\__/_//_/\___/ v4.9.0
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
O\
⇨ http server started on [::]:8080
きちんと動作していることが確認できました。
oapi-codegen
続いてoapi-codegenを試してみます。
インストール
go install github.com/deepmap/oapi-codegen/cmd/oapi-codegen@latest
コード生成
oapi-codegen petstore.yaml > petstore.gen.go
configuration error: package name must be specified
公式のREADMEの通りにするとエラーがでました。
configuration error: package name must be specified
package名を指定しないといけないようです。
oapi-codegen -package petstore petstore.yaml > petstore.gen.go
Echoを使う場合はオプションに「-generate server」を指定します。
※デフォルトでEchoのコードが生成されるので、オプションをつけなくても可
他にも「-generate gin」を指定するとginのコードが生成されます。
petstore.gen.go
// Package petstore provides primitives to interact with the openapi HTTP API.
//
// Code generated by github.com/deepmap/oapi-codegen version v1.12.4 DO NOT EDIT.
package petstore
import (
"bytes"
"compress/gzip"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"path"
"strings"
"github.com/deepmap/oapi-codegen/pkg/runtime"
"github.com/getkin/kin-openapi/openapi3"
"github.com/labstack/echo/v4"
)
// Error defines model for Error.
type Error struct {
Code int32 `json:"code"`
Message string `json:"message"`
}
// Pet defines model for Pet.
type Pet struct {
Id int64 `json:"id"`
Name string `json:"name"`
Tag *string `json:"tag,omitempty"`
}
// Pets defines model for Pets.
type Pets = []Pet
// ListPetsParams defines parameters for ListPets.
type ListPetsParams struct {
// Limit How many items to return at one time (max 100)
Limit *int32 `form:"limit,omitempty" json:"limit,omitempty"`
}
// RequestEditorFn is the function signature for the RequestEditor callback function
type RequestEditorFn func(ctx context.Context, req *http.Request) error
// Doer performs HTTP requests.
//
~~~
~~~
main.goは自分で記述します。
package main
import (
"github.com/labstack/echo/v4"
"net/http"
"petstore-sample/gen"
)
// handler
type Server struct{}
// HelloWorld is a sample data structure to make sure each endpoint return something.
type HelloWorld struct {
Message string `json:"message"`
}
// CreatePets - Create a pet
func (h Server) CreatePets(ctx echo.Context) error {
return ctx.JSON(http.StatusOK, HelloWorld{
Message: "oapi-codegen",
})
}
// ListPets - List all pets
func (h Server) ListPets(ctx echo.Context, params gen.ListPetsParams) error {
return ctx.JSON(http.StatusOK, HelloWorld{
Message: "oapi-codegen",
})
}
// ShowPetById - Info for a specific pet
func (h Server) ShowPetById(ctx echo.Context, petId string) error {
return ctx.JSON(http.StatusOK, HelloWorld{
Message: "oapi-codegen",
})
}
func main() {
e := echo.New()
s := Server{}
gen.RegisterHandlers(e, s)
// Start server
e.Logger.Fatal(e.Start(":8080"))
}
main.go実行
go run main.go
____ __
/ __/___/ / ___
/ _// __/ _ \/ _ \
/___/\__/_//_/\___/ v4.9.0
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
O\
⇨ http server started on [::]:8080
こちらも問題なく動きました!
OpenAPI Generatorとoapi-codegenを比べてみて
下記はOpenAPI Generatorが生成するファイルです。
Dockerfile
README.md
go.mod
handlers/api_pets.go
handlers/container.go
main.go
models/hello-world.go
models/model_error.go
models/model_pet.go
OpenAPI Generatorはmain.goまで生成されるので手軽にAPIサーバーを作ることができます。
その反面、main.goやhandlersの編集をしても、API定義の変更があると再生成されて上書きされてしまいます。
もちろん、ファイルごとに生成の除外指定はできます。
ですが、指定したファイルにはAPI定義の変更内容が反映されないため、手動で変更することになり、自動生成の恩恵が減ってしまいます。
一方でoapi-codegenは生成するファイルをユーザーが指定するので、API定義の変更があってもmain.goなどのユーザーが編集したコードが上書きされることはありません。
そのためOpenAPIを使ってAPIの開発を進めていくにはoapi-codegenの方が向いていると感じました。
またoapi-codegenがEchoを使うことを想定して開発されており、デフォルトでEcho用のメソッドが生成されるのもポイントでした。
以上を踏まえた上で、自分たちのチームではoapi-codegenを採用することになりました。
まとめ
OpenAPI Generatorとoapi-codegenを試してみて、同じコードジェネレータでも大きく違いがあり驚きでした。
開発を進めていけばいろんな知見も溜まると思うので、いずれ共有できればと思います。
Discussion