SwaggerEditorで生成したGo API Serverの処理を実装する

  • はじめに
  • APIの全体像
  • MongoDBの作成
    • Dockerfileの作成
    • Dockerビルド
  • Go-Serverの作成
    • APIの実装
      • CreateUserメソッドの実装
      • GetUserByNameメソッドの実装
      • go/api_user.goの全体
    • Dockerfileの作成
    • Dockerビルド
  • APIの確認
    • docker-compose.yamlの作成
    • コンテナの起動
    • curlによるAPIの確認

はじめに


SwaggerEditorはOpenAPI specを様々な言語に変換することができます。以下の記事では、SwaggerEditorを使って簡単なOpenAPI specを作成し、Go-Serverに変換するところまで紹介しました。

MacにSwaggerEditorをインストールしてOpenAPI specを実装する

しかし、あくまでもAPIの雛形であって、中身の処理が実装されているわけではありません。本記事では、上記の記事で作成したGo-Serverに処理を実装します。

APIの全体像


本記事で作成するAPIの全体像について紹介します。

APIの仕様は以下の通りです。DBにはMongoDBを利用し、apiとdbはそれぞれコンテナで起動します。

  • POST /v2/user/create・・・Context-TypeでJson形式のユーザー情報を受け付け、DBに保管します。
  • GET /v2/user/{username}・・・URLに指定したusernameとマッチするレコードをDBから取得し、表示します。

MongoDBの作成


Dockerfileの作成


MongoDBのDockerfileを作成します。

ディレクトリ構造は以下の通りです。

$ tree mongo/
mongo/
├── Dockerfile
├── init.sh
└── users.json

今回は揮発性のDBコンテナにします。コンテナ起動時に初期データ(users.json)を流し込むinit.shも併せて作成します。

1. 初期データの作成


コンテナ起動時に流し込む初期データをjson形式で作成します。

[
  { "username":	"koratta", "email": "koratta@example.com" },
  { "username":	"koratta2", "email": "koratta2@example.com" }
]

2. 初期実行ファイルの作成


1で作成したJsonファイルをMongoDBに保管するためのスクリプトinit.shを作成します。

mongoimport --authenticationDatabase admin --username root --password password --db api --collection users --drop --file /docker-entrypoint-initdb.d/users.json --jsonArray

3. Dockerfileの作成


init.shをコンテナ起動時に実行するmongoベースのDockerfileを作成します。コンテナ起動時に実行するため、/docker-entrypoint-initdb.dにコピーします。

FROM mongo:latest
COPY users.json /docker-entrypoint-initdb.d/
COPY init.sh /docker-entrypoint-initdb.d/

Dockerビルド


上記で作成したDockerfileをビルドし、コンテナイメージを作成します。

$ docker build -t api-mongo-db:latest mongo/

Go-Serverの作成


APIの実装


APIリクエストのルーティング情報は、go/routers.goに記述されています。

~~go/router.goより抜粋~~
var routes = Routes{ Route{ "Index", "GET", "/v2/", Index, }, Route{ "CreateUser", strings.ToUpper("Post"), "/v2/user/create", CreateUser, }, Route{ "GetUserByName", strings.ToUpper("Get"), "/v2/user/{username}", GetUserByName, }, }

/v2/user/createに対してPOSTされたらCreateUserメソッドを、/v2/user/{username}に対してGETされたらGetUserBynameメソッドを呼び出すようになっています。それぞれのメソッドはgo/api_user.goで定義されているので、それぞれを実装します。

CreateUserメソッドの実装


CreateUserメソッドを以下に示します。

func CreateUser(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	//ヘッダー情報の取得
	if r.Header.Get("Content-Type") != "application/json" {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	length, err := strconv.Atoi(r.Header.Get("Content-Length"))
	body := make([]byte, length)
	length, err = r.Body.Read(body)
	var insert_record User
	err = json.Unmarshal(body[:length], &insert_record)

	//DBへのInsert
	connect := getConnect()
	_, err = connect.Database("api").Collection("users").InsertOne(context.Background(), insert_record)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	w.WriteHeader(http.StatusOK)
}

9~11行目でPOSTされたJson形式のユーザー情報を取得しています。

	length, err := strconv.Atoi(r.Header.Get("Content-Length"))
	body := make([]byte, length)
	length, err = r.Body.Read(body)

12~13行目でUser構造体のinsert_recordに変換しています。

	var insert_record User
	err = json.Unmarshal(body[:length], &insert_record)

このUser構造体は、OpenAPI specから生成されていて、go/model_user.goに記述されています。

package swagger

type User struct {

	Username string `json:"username,omitempty"`

	Email string `json:"email,omitempty"`
}

16~17行目でMongoDBに保管しています。getConnectメソッドはMongoDBとの接続を確立するメソッドで、go/api_user.goの全体に載せています。

	connect := getConnect()
	_, err = connect.Database("api").Collection("users").InsertOne(context.Background(), insert_record)

GetUserByNameメソッドの実装


GetUserByNameメソッドを以下に示します。

func GetUserByName(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	//検索ユーザー情報の取得
	user := strings.Replace(r.URL.Path, "/v2/user/", "", 1)

	//user情報の取得
	var output User
	connect := getConnect()
	filter := bson.D{{"username", user }}
	err := connect.Database("api").Collection("users").FindOne(context.Background(), filter).Decode(&output)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	//Jsonの出力
	json.NewEncoder(w).Encode(output)

	w.WriteHeader(http.StatusOK)
}

5行目で検索条件となるユーザー名を取得しています。今回のAPIの仕様は、URLのパスに指定されたユーザー名の情報を表示するので、URLパスからユーザー名を取得しています。

user := strings.Replace(r.URL.Path, "/v2/user/", "", 1)

9~11行目で、URLに指定されたユーザー名を基にMongoDBから情報を取得しています。

	connect := getConnect()
	filter := bson.D{{"username", user }}
	err := connect.Database("api").Collection("users").FindOne(context.Background(), filter).Decode(&output)

go/user_api.goの全体


user_api.goの全体は以下の通りです。

/*
 * Koratta Test API
 *
 * This is sample api
 *
 * API version: 1.0.0
 * Contact: koratta@example.com
 * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
 */

package swagger

import (
	"net/http"
	"log"
	"time"
	"encoding/json"
	"context"
	"strconv"
	"strings"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"go.mongodb.org/mongo-driver/bson"
)

func CreateUser(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	//ヘッダー情報の取得
	if r.Header.Get("Content-Type") != "application/json" {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	length, err := strconv.Atoi(r.Header.Get("Content-Length"))
	body := make([]byte, length)
	length, err = r.Body.Read(body)
	var insert_record User
	err = json.Unmarshal(body[:length], &insert_record)

	//DBへのInsert
	connect := getConnect()
	_, err = connect.Database("api").Collection("users").InsertOne(context.Background(), insert_record)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	w.WriteHeader(http.StatusOK)
}

func GetUserByName(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	//検索ユーザー情報の取得
	user := strings.Replace(r.URL.Path, "/v2/user/", "", 1)

	//user情報の取得
	var output User
	connect := getConnect()
	filter := bson.D{{"username", user }}
	err := connect.Database("api").Collection("users").FindOne(context.Background(), filter).Decode(&output)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	//Jsonの出力
	json.NewEncoder(w).Encode(output)

	w.WriteHeader(http.StatusOK)
}

func getConnect() *mongo.Client{
	//MongoDBの認証情報
	credential := options.Credential{
		Username: "root",
		Password: "password",
	}
	//MongoDBへの接続
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	connect, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://db:27017").SetAuth(credential))
	if err != nil { log.Fatal(err) }

	return connect
}

Dockerfileの作成


Dockerfileを作成します。

ディレクトリ構造は以下の通りです。

$ tree api
api
├── Dockerfile
└── src
    ├── api
    │   └── swagger.yaml
    ├── go
    │   ├── README.md
    │   ├── api_user.go
    │   ├── logger.go
    │   ├── model_user.go
    │   └── routers.go
    └── main.go

src配下のファイルは、MacにSwaggerEditorをインストールしてOpenAPI specを実装するで作成したソースコードです。

Dockerfileは以下の通りです。

FROM golang:latest
# gouserの作成
RUN useradd -m -s /bin/bash gouser
# コンテナ作業ディレクトリの変更
WORKDIR /go/src/api
RUN chown gouser:gouser /go/src/api
# モジュールのダウンロード
RUN go get -u github.com/gorilla/mux &&\
    go get -u "go.mongodb.org/mongo-driver/mongo"
# ホストOSの ./src の中身を作業ディレクトリにコピー
COPY --chown=gouser:gouser ./src .
# go build
RUN go build -o koratta-api &&\
    chown gouser:gouser api
# gouserに変更
USER gouser
# API実行コマンドの実行
CMD ["/go/src/api/koratta-api"]

必要なモジュールのgorilla/muxとmongo-driver/mongoをインストールし、gouserに実行権限を与えてgouserで実行するようにしています。root権限でも当然実行できます。

注意点は、main.goのimportパッケージのsw "go"部分です。コンテナの場合デフォルトで$GOPATH=/go/srcなので、上記のDockerfileの場合sw "api/go"になります。

APIの確認


docker-compose.yamlの作成


MongoDBとAPIのコンテナを起動するためのdocker-compose.yamlを作成します。

version: '3'
services:
  db:
    container_name: db
    image: api-mongo-db:latest
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: password
    ports:
      - "27017:27017"
  api:
    container_name: api
    image: api:latest
    depends_on:
      - db
    links:
      - db
    ports:
      - "8080:8080"

コンテナの起動


docker-composeを使ってコンテナを起動します。

$ docker-compose up -d

curlによるAPIの確認


作成したAPIの挙動を確認します。

初期データとして保管されているレコードをAPI経由で確認すると、以下の通り、指定したユーザー名に該当する情報を取得できます。

$ curl -X GET "http://localhost:8080/v2/user/koratta"
{"username":"koratta","email":"koratta@example.com"}

保管されていないユーザーは、当然取得できません。

$ curl -X GET "http://localhost:8080/v2/user/koratta5
$

従って、APIを使ってユーザー情報を保管します。

$ curl -X POST "http://localhost:8080/v2/user/create" -H "accept: application/xml" -H "Content-Type: application/json" -d "{ \"username\": \"koratta5\", \"email\": \"koratta5@example.com\"}"

再度確認すると、ユーザー情報を確認することができます。/v2/user/createのAPI経由でDBに保管できていることが確認できました。

$ curl -X GET "http://localhost:8080/v2/user/koratta5"
{"username":"koratta5","email":"koratta5@example.com"}

以上です。