go-swaggerで作るAPIサーバー

こんにちは。株式会社レスキューナウの新プロダクトチームで主にバックエンドを担当している本田です。
現在、バックエンドではGo言語を採用し、go-swaggerというswaggerからGoコードを自動生成してくれるライブラリを使って、各APIを開発しています。
同じようなライブラリにopenapi-generatorというのもあります。

今回はこのgo-swaggerを使って実際に簡単なAPIを実装してみてみます。

go-swaggerインストール

まずはgo-swaggerをインストールしていきます。 Macの場合はHomebrewでインストールできます。 もちろん go install や他のインストール方法もあります。

$ brew tap go-swagger/go-swagger
$ brew install go-swagger

ディレクトリ作成

次にプロジェクト用のディレクトリの作成と、モジュール形式で進めたいので、go mod initを実行します。

$ mkdir swagger-practice
$ go mod init swagger-practice

さらに、src、gen、swaggerディレクトリを作成しておきます。

 tree
.
├── go.mod
├── go.sum
├── src
│   └── gen     
└── swagger

swagger.yaml

ディレクトリの準備ができたら、swaggerディレクトリ内にswagger.yamlファイルを用意します。

└── swagger
    └── swagger.yaml
  1 swagger: "2.0"
  2 info:
  3   title: Swagger Practice
  4   version: 1.0.0
  5 host: localhost
  6 schemes:
  7   - http
  8 paths:
  9   /hello:
 10     get:
 11       operationId: hello
 12       responses:
 13         200:
 14           description: OK
 15           schema:
 16             type: string
 17             description: response text
 18 
 19  

9行目でエンドポイントを指定し、10行目でメソッド、13行目以降でレスポンスステータスやレスポンスボディの指定をしています。
実際はパスパラメータやクエリパラメータなど、もっといろんな指定もできますが、今回は簡単に実装をするため、非常にシンプルな記述になっています。
このAPIの場合 localhost/helloがエンドポイントになります。

作成したファイルはeditor.swagger.ioなどのPreviewで確認することができます。

ソースコードの確認と生成

swagger.yamlファイルの記述ができたら、Goコードの生成をしていきます。
生成するまえに、swagger validateコマンドで、記述に間違いがないかチェックできます。

$ swagger  validate swagger/swagger.yaml 
2022/07/11 16:19:45 
The swagger spec at "swagger/swagger.yaml" is valid against swagger specification 2.0

記述に誤りがあると下記のようにどこに誤りがあるのか教えてくれます。

$ swagger  validate swagger/swagger.yaml 

The swagger spec at "swagger/swagger.yaml" is invalid against swagger specification 2.0.
See errors below:
- "paths./hello.get.responses.200" must validate one and only one schema (oneOf). Found none valid
- paths./hello.get.responses.200.description in body is required

確認して問題なければコードを生成しましょう。
swagger generate serverコマンドを使用します。
オプションは公式ドキュメントで確認できます。普段は"--exclude-main"を指定して、main.goを生成させない(上書きされない)ようにしているのですが、今回はmain.goも生成させます。

$ swagger generate server  -t src/gen -f swagger/swagger.yaml 
2022/07/11 16:28:05 validating spec /Users/kazuyuki/rescue/practice/swagger/swagger.yaml
~
~
2022/07/11 16:28:05 executed template asset:serverConfigureapi
2022/07/11 16:28:05 Generation completed!

For this generation to compile you need to have some packages in your GOPATH:

        * github.com/go-openapi/runtime
        * github.com/jessevdk/go-flags

You can get these now with: go get -u -f src/gen/...

生成コマンドを実行するとgenディレクトリに各種ディレクトリやファイルが生成されました。

├── src
│   ├── gen
│   │   ├── cmd
│   │   │   └── swagger-practice-server
│   │   │       └── main.go
│   │   └── restapi
│   │       ├── configure_swagger_practice.go
│   │       ├── doc.go
│   │       ├── embedded_spec.go
│   │       ├── operations
│   │       │   ├── hello.go
│   │       │   ├── hello_parameters.go
│   │       │   ├── hello_responses.go
│   │       │   ├── hello_urlbuilder.go
│   │       │   └── swagger_practice_api.go
│   │       └── server.go

試しにmain.goを実行してみる

main.goも生成されたので、試しに実行してみましょう。
先にgo mod tidyで必要なパッケージをインストールしておくのも忘れないようにしましょう。

$ go mod tidy
go: finding module for package github.com/go-openapi/strfmt
go: finding module for package github.com/go-openapi/swag
~
~
$ go run src/gen/cmd/swagger-practice-server/main.go --host 0.0.0.0 --port 8888
2022/07/11 16:32:27 Serving swagger practice at http://[::]:8888

無事APIサーバーが起動しました。
エンドポイントlocalhost{:port}/helloにアクセスしてみると、"operation operations.Hello has not yet been implemented"(未実装です)という画面が…。

ハンドラの実装と登録

APIのリクエストを受け付けた後、"どのデータをどう処理をして、どんなデータで返却するか"という部分は、go-swaggerの自動生成ではカバーしておらず、自分たちで実装する必要があります。

srcディレクトリ配下にhandlerディレクトリおよびget_hello_handler.goファイルを用意します。

├── src
│   ├── gen
│   │   ├── cmd
│   │   └── restapi
│   └── handler
│       └── get_hello_handler.go

get_hello_handler.goファイルの中身。
"Hello Swagger"という文字列をレスポンスする記述になっています。

  1 package handler
  2 
  3 import (
  4         "github.com/go-openapi/runtime/middleware"
  5         "swagger-practice/src/gen/restapi/operations"
  6 )
  7 
  8 func GetHello(p operations.HelloParams) middleware.Responder {
  9         return operations.NewHelloOK().WithPayload("Hello Swagger")
 10 }

自動生成されたファイルで、src/gen/restapi/configure_swagger_practice.goも編集します。
ファイルの1行目に「// This file is safe to edit. Once it exists it will not be overwritten 」という記述があるファイルは、生成コマンドを実行しても再生成されず、自由に編集することができます。

36
37          api.JSONProducer = runtime.JSONProducer()
38
39          //if api.HelloHandler == nil {
40          //      api.HelloHandler = operations.HelloHandlerFunc(func(params operations.HelloParams) middleware.Responder {
41          //              return middleware.NotImplemented("operation operations.Hello has not yet been implemented")
42          //      })
43          //}
44
45          api.HelloHandler = operations.HelloHandlerFunc(handler.GetHello)
46

39行目〜43行目をコメントアウトして、45行目の1行を追加します。
先程作ったget_hello_handler.goファイルのGetHello関数を呼びだしています。

 5  import (
 6          "crypto/tls"
 7          "net/http"
 8
 9          "github.com/go-openapi/errors"
10          "github.com/go-openapi/runtime"
11          "swagger-practice/src/gen/restapi/operations"
12          "swagger-practice/src/handler"
13  )

先頭のimportも忘れず追加しておきます。

いざ実行

ここまでできれば、準備OKです。実際にAPIサーバーを立ち上げてみましょう。

$ go run src/gen/cmd/swagger-practice-server/main.go --host 0.0.0.0 --port 8888
2022/07/11 16:32:27 Serving swagger practice at http://[::]:8888

エンドポイントlocalhost{:port}/helloにアクセスしてみると、"Hello Swagger"という文字列がきちんと表示されています。
期待したレスポンスが返ってきいるのを確認できました。

今回は200だけのレスポンスしか記述していませんでしたが、実際には400番や500番エラーもswagger.yamlに定義し、エラーに合わせたレスポンスも定義します。
また、今回はGETメソッドのみでしたが、POSTやPUT、DELETEメソッドももちろん使用することができるので、swagger.yamlでいろんなAPIを簡単に定義できます。

まとめ

いかがでしたでしょうか。go-swaggerを用いて、簡単なAPIを実装してみました。
swaggerとgo-swaggerは、API定義の記述から、実装まで簡単にできるツールなので、ご興味のある人は一度試してみて欲しいです。

最後まで読んでいただき、ありがとうございました。

最後に

現在、レスキューナウでは、災害情報の提供、災害情報を活用した安否確認サービスなどのWebサービスの開発エンジニアを募集しています!
社員・フリーランスに関わらず、参画後に安心してご活躍できることを目指し、応募された方の特性・ご希望にマッチしたチームをご紹介します。
ちょっと話を聞いてみたい、ぜひ応募したい、など、当社にご興味を持っていただけましたら、お気軽にエントリーください!!