フューチャー技術ブログ

go-swaggerでhello world

The Gopher character is based on the Go mascot designed by Renée French.

TIG DXチームの伊藤真彦です。
今回はgo-swaggerの具体的な実装方法を紹介します。

目次

  • はじめに
  • go-swaggerのインストール
  • swagger.yamlを準備する
  • ソースコードをビルドする
  • 試しにサーバーを立ち上げてみる
  • ハンドラを実装する
  • ついにhello world完了

はじめに

最近の私のメイン業務はgo-swaggerを用いたAPI開発です。
go-swaggerはOpenAPI(Swagger) からGoのコードを生成するライブラリです。
フューチャーでは複数案件での採用実績があり、この技術ブログでも様々な記事が書かれています。

しかし、技術選定としての参考情報やtips集はあるものの、どのように実装していけばAPIが動くのかを理解するドキュメントは少なく、いざ実装となると学習コストがかかってしまいます。
そこで、今回はストーリーベースでの実装手順を説明します。

go-swaggerのインストール

go-swaggerは開発環境にインストールして使用します。
下記コマンドでインストールできます。

go get -u github.com/go-swagger/go-swagger/cmd/swagger

その他にも様々なインストール方法があります。

  • brew、apt、wget等のコマンドを経由してインストール
  • ソースコード、バイナリファイルのダウンロード
  • go installコマンドを経由したインストール
  • dockerコンテナとしての実行

Dockerコンテナ以外は概ね開発環境のOSによる入手経路の違い程度の認識で差し支えないと思います。詳しくはInstallingを確認してください。

リリースページからお使いのOSに応じた実行ファイルをダウンロードしてインストールすることも可能です。

アプリケーションをコンテナイメージの上で実行する場合、公式のコンテナイメージをマルチステージビルドに用いる事も可能ですね、夢が広がります。
方法は様々ですが、インストール後にswaggerコマンドが利用可能になります。(コンテナ形式での導入を除く)

swagger.yamlを準備する

OpenAPIの仕様に従ってアプリケーションが生成される以上、まずはAPIの仕様を定義するファイルが無いと始まりません。
まずはswagger.yamlを作成します。

swagger.yamlの書き方はOpenAPI Specificationなどに記載があります。
スキーマファースト開発のためのOpenAPI(Swagger)設計規約もあわせてお読みください。
swagger.yamlの書き方についての詳細な説明は今回は省略します。

hello worldのためのサンプルとなるswagger.yamlはgo-swaggerのリポジトリに用意されています。
tutorials/custom-serverを例に説明します。

swagger.yaml
---
swagger: '2.0'
info:
version: 1.0.0
title: Greeting Server
paths:
/hello:
get:
produces:
- text/plain
parameters:
- name: name
required: false
type: string
in: query
description: defaults to World if not given
operationId: getGreeting
responses:
200:
description: returns a greeting
schema:
type: string
description: contains the actual greeting as plain text

サンプルの中では特にシンプルな構成です。

paths:
/hello:
get:

上記部分に記載の通り、{host-name}/helloにGETでアクセスする場合のリクエストパラメータ、レスポンスが定義されています。
レスポンスのフォーマットはtext/plain
URLのクエリパラメータにnameを持たせることができる。
成功した場合のHTTPレスポンスステータスは200 OK
レスポンスのbodyに単一の文字列が返ってくる。
…という事がswagger.yamlの内容から推測できます。

作成したファイルはOpenAPI Previewで確認することが可能です。Chrome拡張vscode向けのプラグインなどで利用可能です。editor.swagger.ioのようなウェブサイトとしても公開されています。
vimプラグインもありますね…素晴らしい。

このswagger.yamlを元に実際にソースコードをビルドしてみましょう。

ソースコードをビルドする

ディレクトリの構成は自由ですが、私のチームでは自動生成されたコードはserver/genに配置される構成をとっています。
下記のような構成でserver/genまでディレクトリを作成します。

.
├──swagger
| └─swagger.yaml
└──server
└─gen

serverディレクトリに移動し、下記コマンドでソースコードをビルドします。

swagger generate server -a factory -A factory -t gen f ./swagger/swagger.yaml

オプションの詳細についてはgo-swaggerを用いたWebアプリケーション開発Tips19選のTips2をご覧ください。

今回は--exclude-mainを使用せずにmain.goも生成してもらいます。コマンドの実行に成功すると、server/gen配下に各種ファイルが生成されます。

試しにサーバーを立ち上げてみる

main.goを実行することでサーバーが起動します。

serverディレクトリ上で下記コマンドを実行します。

go run gen/cmd/factory-server/main.go --host 0.0.0.0 --port 3000

コマンド実行後にブラウザでlocalhost:3000/helloにアクセスしてみましょう。

エラーが出ます、hello worldまではあと一歩ですが、まだやることがあります。

ハンドラを実装する

自動生成したコードだけではAPIサーバは完成しません。

なぜならAPIが表示したいデータをどこから用意し、どのような形式でレスポンスに返すかはswagger.yamlへの記載だけではカバーしきれないからです。例えばリクエストを元にデータベースから情報を取得、返却するAPIを構築するとします。

データベース層はRDSでしょうか、NoSQLでしょうか、クラウド上のマネージドDBでしょうか、はたまたオンプレミスでしょうか。取りうる可能性は無限大です。そのためデータベースへのアクセスやレスポンスデータの加工は自前で実装する必要があるわけですね。

今回は下記のようなファイルを用意します。

server/get_greeting_handler.go
package server

import (
"github.com/go-openapi/runtime/middleware"
"github.com/example-xxxxx/myapp-name/server/gen/restapi/factory"
)

func GetGreeting(p factory.GetGreetingParams) middleware.Responder {
payload := *p.Name
return factory.NewGetGreetingOK().WithPayload(payload)
}

※importするパスはご自身のGitHubリポジトリになります。

.
├──swagger
| └─swagger.yaml
└──server
├─gen
└─get_greeting_handler.go

今回は上記のようなディレクトリ構成で配置しました。

GetGreetingParamsNewGetGreetingOK()などが自動生成された関数、及び構造体です。GetGreetingParamsはリクエストパラメータであり、p.Nameでクエリパラメータの内容が取得できます。生成されたコードのお作法に則りハンドラを実装します。今回は受け取ったクエリパラメータをそのままレスポンスとして返してみます。このファイルの変数payloadを任意の文字列にすると実際にレスポンスが変化します。

ファイルの用意ができたら実装したハンドラ関数をアプリケーションが実行するように設定します。実は自動生成したファイルの中には、手動での変更を認めないものと、認めるものが存在します。server\gen\restapi\configure_factory.goが手動での変更を許可するファイルです。書き換えても良いファイルは先頭行に// This file is safe to edit. Once it exists it will not be overwrittenとコメントされています。

さてこのファイルの下記の部分を見てみましょう。

if api.GetGreetingHandler == nil {
api.GetGreetingHandler = factory.GetGreetingHandlerFunc(func(params factory.GetGreetingParams) middleware.Responder {
return middleware.NotImplemented("operation factory.GetGreeting has not yet been implemented")
})
}

api.GetGreetingHandlerがnilのままではnot yet been implementedと出力する設定になっています。
改めて先ほどのエラーを見てみましょう、細かい表示はともかく同じような内容のメッセージが出力されています。

この部分を書き換えるか、ここより先に評価される行で下記のようにapi.GetGreetingHandlerを定義しましょう。

api.GetGreetingHandler = factory.GetGreetingHandlerFunc(server.GetGreeting)

configure_factory.goのimport文の更新も必要です。

import (
"crypto/tls"
"net/http"

"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"

+ "github.com/example-xxxxx/myapp-name/server"
"github.com/example-xxxxx/myapp-name/server/gen/restapi/factory"
)

ここまで書けたら今度こそサーバーを起動して動かしてみましょう。

ついにhello world完了

先ほど書いた内容と同じ手順でサーバーを立ち上げます。

go run gen/cmd/factory-server/main.go --host 0.0.0.0 --port 3000

ブラウザでlocalhost:3000/hello?name=hello-go-swaggerにアクセスします。

期待したレスポンスが返ってきました。ちなみに記事の通りのget_greeting_handler.goでは、nameが与えられていない場合のエラーハンドリングが実装されていないため、?name=hello-go-swaggerをURLに含めないと500番台のエラーすら返せずに処理に失敗してしまいます。

実際には400番、500番のエラーもswagger.yamlに定義し、どのような場合にどのエラーを返すかをハンドラに実装していく必要があります。(どの程度不正なリクエストを許容するのかといった柔軟性も、ハンドラで要件に合わせ実装していく形になります。)

今回はhello world編ということでここまでになります、是非皆さんも実際に試してみてください。

go-swaggerの関してはこちらの記事もおすすめです