🎶

OpenAPIからGo言語のコードを自動生成してくれるツールを試してみた

2022/12/07に公開

はじめに

Go言語のフレームワークであるEchoを用いてAPIサーバーを開発することになり、OpenAPIからEchoのコードを自動生成するツールを試してみたので、まとめてみました。

今回はOpenAPI Generatoroapi-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