1. Qiita
  2. 投稿
  3. Go

florestを使ってWebAPIを作ってみた

  • 8
    いいね
  • 0
    コメント

この記事はGo (その3) Advent Calendar 2016の9日目の記事です。

マイクロサービスの調査のいっかんとして、go製のWebAPI用フレームワークflorestというフレームワークを試してみたので、ここにまとめる。

前提条件

ここでは以下のバージョンを使用した。

  • go 1.7.3
  • florest (githubの2016/11/7のmasterのhead)
  • gingko

gingkoのインストールは以下

$ go get github.com/onsi/ginkgo/ginkgo
$ go get github.com/onsi/gomega

プロジェクトの作成

まず、florest-coreのリポジトリをローカルにcloneします。

$ git clone https://github.com/jabong/florest-core

さらにcloneしたflorest-coreのフォルダに移動して、アプリの雛形をつくります。
make newappを実行する際に環境変数NEWAPPに展開先のフォルダを指定します。

$ cd florest-core
$ make newapp NEWAPP="~/newapp"
building new app
copying dependent libs
$ cd ~/newapp

これでホームフォルダの下にnewappが作られました。

ファイルの構成は以下のような感じです。

.
├── Makefile
├── _libs
├── config
│   ├── logger
│   │   └── logger.json
│   └── newapp
│       └── conf.json
├── main.go
├── scripts
│   ├── README.md
│   ├── logger.sh
│   └── swagger.sh
└── src
    ├── README.md
    ├── common
    │   ├── appconfig
    │   │   └── application_config.go
    │   └── appconstant
    │       └── error_codes.go
    ├── hello
    │   ├── api_definition.go
    │   ├── data_structures.go
    │   ├── hello_world.go
    │   ├── hello_world_health_checker.go
    │   └── swagger.go
    └── test
        ├── coverage.sh
        ├── servicetest
        │   ├── config_initialize.go
        │   ├── logger_initialize.go
        │   ├── service_initialize.go
        │   ├── service_test.go
        │   ├── service_test_helper.go
        │   ├── test_web_server.go
        │   └── testdata
        │       ├── testconf.json
        │       └── testloggerAsync.json
        └── utils
            ├── request_helper.go
            └── response_status_helper.go

さらに実行エラーなどを吐き出すログ用のフォルダを作成します。
デフォルトでは、/var/log/アプリ名に作られるので、sudoを使ってフォルダを作成したあとに、florestを実行するユーザーに所有者を変更しておきます。

$ sudo mkdir /var/log/newapp
$ sudo chown myuser /var/log/newapp

最低限の準備ができたので、試しにデプロイします。

$ cd ~/newapp
$ make deploy

binフォルダ配下に実行ファイルが作成されます。binフォルダに移動して実行します。

$ cd bin
$ ./newapp
APPLICATION BEGIN
2016/11/15 00:14:41 Web server Initialization begin
2016/11/15 00:14:41
Initializing Config
config &{AppName: AppVersion: ServerPort: LogConfFile: MonitorConfig:{APPName: APIKey: APPKey: AgentServer: Platform: Verbose:false Enabled:false MetricsServer:} Performance:{UseCorePercentage:0 GCPercentage:0} DynamicConfig:{Active:false RefreshInterval:0 ConfigKey: CacheKey:} HTTPConfig:{MaxConn:0 MaxIdleConns:0 ResponseHeaderTimeout:0 DisableKeepAlives:false} Profiler:{Enable:false SamplingRate:0} ResponseHeaders:{CacheControl:{ResponseType: NoCache:false NoStore:false MaxAgeInSeconds:0}} ApplicationConfig:0xc4200cbe20 AppRateLimiterConfig:<nil>}
2016/11/15 00:14:41 Application Config Created
2016/11/15 00:14:41
Global Config=&{AppName:newapp AppVersion:1.0.0 ServerPort:8080 LogConfFile:conf/logger.json MonitorConfig:{APPName:newapp APIKey: APPKey: AgentServer:datadog:8125 Platform:DatadogAgent Verbose:false Enabled:false MetricsServer:datadog:8065} Performance:{UseCorePercentage:100 GCPercentage:1000} DynamicConfig:{Active:false RefreshInterval:0 ConfigKey: CacheKey:} HTTPConfig:{MaxConn:0 MaxIdleConns:0 ResponseHeaderTimeout:0 DisableKeepAlives:false} Profiler:{Enable:false SamplingRate:0} ResponseHeaders:{CacheControl:{ResponseType: NoCache:false NoStore:false MaxAgeInSeconds:0}} ApplicationConfig:0xc4200cbe20 AppRateLimiterConfig:<nil>}
2016/11/15 00:14:41
Application Config=&{Hello:{ResponseHeaders:{CacheControl:{ResponseType: NoCache:false NoStore:false MaxAgeInSeconds:0}}}}
No of Cpu Core to be Used = 4

8080ポートにアクセスしてみるとサーバが起動していることがわかります。

エンドポイントの追加

bookという新しいエンドポイントを追加します。

必要なファイルを用意

最初に、srcフォルダの下のhelloフォルダをコピーして、ファイルをリネームして、
内部の変数名などの値を全て置換します。

$ cp -R src/hello src/book
$ find src/book -name "hello_world*" -exec rename s/hello_world/book/ {} +
$ find src/book -name "*.go" -exec sed -i 's/helloWorld/book/g' {} +
$ find src/book -name "*.go" -exec sed -i 's/hello/book/g' {} +
$ find src/book -name "*.go" -exec sed -i 's/HelloWorld/Book/g' {} +
$ find src/book -name "*.go" -exec sed -i 's/Hello/Book/g' {} +

main.goへのAPIの追加

次にmain.goに、さきほど複製してリネームしたbook APIへのimportの追加と、
florestのserviceにBookAPIを追加します。

import (
        "common/appconfig"
        "common/appconstant"
        "fmt"
        "hello"
        "book" // 追加

        "github.com/jabong/florest-core/src/core/service"
)

//main is the entry point of the florest web service
func main() {
        fmt.Println("APPLICATION BEGIN")
        webserver := new(service.Webserver)
        registerConfig()
        registerErrors()
        registerAllApis()
        webserver.Start()
}

func registerAllApis() {
        service.RegisterAPI(new(hello.HelloAPI))
        service.RegisterAPI(new(book.BookAPI)) // 追加
}

エンドポイントのパスの設定

src/book/api_definition.go

で、ホスト名以下のパス名を定義します。
さらに、Book IDを指定して、指定のIDの情報が取得できるように、パス内にリクエストパラメータbookIdを追加します。

func (a *BookAPI) GetVersion() versionmanager.Version {
        return versionmanager.Version{
                Resource: "BOOK", //エンドポイントの終端のパス名
                Version:  "V1", // アプリ名の次にくるバージョン名
                Action:   "GET", // リクエストメソッド
                BucketID: constants.OrchestratorBucketDefaultValue, //todo - should it be a constant
                Path:     "{bookId}", // bookIdを取得できるようにする
        }
}

上記の設定により

http://ホスト名:8080/newapp/v1/book/{bookId}

というパスが定義されます。

newappの部分は、bin/conf/conf.jsonの、ルートのAppNameキーで定義されている値になります。

リクエストを受けた時の処理の追加

src/book/book.goの、func (a Book) Execute(io workflow.WorkFlowData) (workflow.WorkFlowData, error)にBook APIの処理ロジックを記載します。

引数のioにはリクエストの情報とレスポンスの情報が含まれます。

リクエストパラメータの取得

ioIOData.Getメソッドを使って値を取得します。

引数に入る値は、request_response_constants.goに定義されている定数を使います。PathParamsを取得したいので、引数にconstants.PathParams
を入力します。

        pathp, _ := io.IOData.Get(constants.PathParams)
        fmt.Println(pathp)

deployして実行します。サーバの起動を確認したら、コンソールからcurlでbookIdを指定してリクエストを投げてみます。

$ curl -XGET "http://localhost:8080/newapp/v1/book/123" | jq .
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   158  100   158    0     0  22829      0 --:--:-- --:--:-- --:--:-- 26333
{
  "status": {
    "httpStatusCode": 200,
    "success": true,
    "errors": null
  },
  "data": null,
  "_metaData": {
    "urlParams": {},
    "apiMetaData": {}
  }
}

サーバを起動しているコンソール側のスタンダードアウトには

123

と、リクエストパラメータに指定した値が取得できていることがわかります。

レスポンスの記述

処理した結果などをレスポンスの内容に記述するには、ioIOData.Setメソッドを使います。

こちらも先ほどと同じように、第一引数には、request_response_constants.goの定数から選択します。

レスポンスのJSONのdataキーの値に結果をレンダリングするには、constants.Resultを使います。

        io.IOData.Set(constants.Result, "test")

同様に、DeployしたあとにWebAPIのサーバを起動して、curlを使ってレスポンス値を確認します。

$ curl -XGET "http://localhost:8080/newapp/v1/book/123" | jq .
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   125  100   125    0     0   8965      0 --:--:-- --:--:-- --:--:--  9615
{
  "status": {
    "httpStatusCode": 200,
    "success": true,
    "errors": null
  },
  "data": "test",
  "_metaData": {
    "urlParams": {},
    "apiMetaData": {}
  }
}

第2引数に指定した文字列testがdataキーに入っていることがわかります。

Swaggerの出力

依存のインストール

Swaggerを扱うために、まずgoのswaggerライブラリをインストールします。

$ go get github.com/yvasiyarov/swagger

Swaggerの定義を記述

src/book/swagger.go

にAPIの仕様を記述します。

package の記述の上に、APIのバージョンとbasePathを記述します。

// @APIVersion 1.0.0
// @basePath /newapp/v1
package book

次にtestを取得するメソッドを記述します。

// @Title test
// @Description get test book
// @Accept  json
// @Param   BookId     path    string     true        "book_id"
// @Router /book/{BookId} [get]
func test() {}

メソッド名は、nicknameとして出力されます。
そのほかの定義については、

Whats swagger

を確認してください。

Swaggerの生成

Swaggerを生成するために、以下のコマンドを実行します。

$ make deploy SWAGGER=book

サーバを起動します。そうすると、以下のURLにアクセスすることで、swagger形式のJSONにアクセスできます。

http://localhost:8080/swagger/v1/book/apidocs.json

ここまでできれば、あとはSwagger UIを使ってドキュメントを生成したりなど、やり放題です。

その他に提供される機能

ここまで、最低限の機能について、紹介していました。

florestで用意されている他の機能としては、

  • MySQL Components
  • mongodb Components
  • A/B Test
  • 非同期のLogging (Logstash形式のJSONに対応)
  • Datadogを使ったレスポンスタイムなどのモニタリング
  • 各メソッドなどコードの実行時間の計測
  • バックグラウンドで非同期実行されるWorker pool
  • エラーやタイムアウトなどHttp Utilitiy
  • Fault toleranceな機能
  • 単純なバリデーション
  • Rate limit

などが提供されます。

まとめ

駆け足でまとめたため、説明不足の部分が多々ありますが、go初心者の自分には、デザインパターンなど学ぶ部分が多々あり勉強になりました。