この記事は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
にはリクエストの情報とレスポンスの情報が含まれます。
リクエストパラメータの取得
io
のIOData.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
と、リクエストパラメータに指定した値が取得できていることがわかります。
レスポンスの記述
処理した結果などをレスポンスの内容に記述するには、io
のIOData.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
として出力されます。
そのほかの定義については、
を確認してください。
Swaggerの生成
Swaggerを生成するために、以下のコマンドを実行します。
$ make deploy SWAGGER=book
サーバを起動します。そうすると、以下のURLにアクセスすることで、swagger形式のJSONにアクセスできます。
ここまでできれば、あとはSwagger UIを使ってドキュメントを生成したりなど、やり放題です。
その他に提供される機能
ここまで、最低限の機能について、紹介していました。
florestで用意されている他の機能としては、
- MySQL Components
- mongodb Components
- A/B Test
- 非同期のLogging (Logstash形式のJSONに対応)
- Datadogを使ったレスポンスタイムなどのモニタリング
- 各メソッドなどコードの実行時間の計測
- バックグラウンドで非同期実行されるWorker pool
- エラーやタイムアウトなどHttp Utilitiy
- Fault toleranceな機能
- 単純なバリデーション
- Rate limit
などが提供されます。
まとめ
駆け足でまとめたため、説明不足の部分が多々ありますが、go初心者の自分には、デザインパターンなど学ぶ部分が多々あり勉強になりました。