3行で言うと
- herokuが作ってる prmd を使って、JSON SchemaからAPIドキュメントを出力したよ!
- スキーマ定義から、GoのAPI実装コードも出力するツールを作ったらめっちゃ捗るよ!
- Goのバリデーション用のライブラリも作ったよ!
今回作ったものの概要とサンプルコード
概要
以前から、APIを開発する上で、以下のようなことが課題となっていました。
- そもそもドキュメント書くのがつらい
- それもあって、ドキュメントより先にコードが変わってしまう
- ドキュメントと実装の状況の違いが把握しづらい
また、ロジックがそんなに複雑ではないAPIでは、実装の作業は
- リクエストデータのバリデーション
- 出力データの整形 (フィルタリング)
の2つの作業が大きな割合を占めます。
APIの定義ファイルからドキュメントと、バリデーションや出力データ整形のコードを自動生成できれば、大幅に効率が上がると思い実装してみました。
今回実装した仕組みの全体の処理の流れは以下の様になっています。
- prmd を使って、schema.json (JSON Hyper-Schemaドキュメント) を生成
prmd combine
- schema.json から、APIドキュメント (schema.md) を生成
prmd doc
- schema.json から、Goのサーバー側実装を生成 (独自実装)
サンプルコード
今回の記事のサンプルコードは https://github.com/wcl48/go-api-generation-sample にあります。
サンプルプロジェクト内のディレクトリ、ファイルは以下の様な構成です
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
APIの定義 : JSON Hyper-Schema
Rest API を定義するための仕様はいくつか世の中にあって、代表的なものとしては
- JSON Hyper-Schema http://json-schema.org/
- RAML http://raml.org/
- apiblueprint http://apiblueprint.org/
を見つけました。
そこそこ世の中に浸透している「JSON Schema」に対する拡張であり、関連ライブラリなども豊富なことから、今回は JSON Hyper-Schema を選択しました。
ドキュメンテーション: prmd by heroku
heroku が作っている、 prmd というツールがあります。prmd を使うと、APIの定義をファイルを分割して管理でき、JSON Hyper-Schema の生成とバリデーション、ドキュメントの生成が行えます。
herokuのAPIドキュメント もこのツールをベースに生成されているようです。 元のスキーマ定義 も公開されています。
サンプルコードでは、生成されたドキュメントが、 test/schema.md にあります。 元のスキーマ定義は、 test/schemata/hoge.yaml です。
どんなGoのコードを生成するか
ドキュメントは無事生成できたので、次にGoのコードの生成について考えます。
要件としては、概ね以下のようなものとしました。
- 利用するライブラリは、標準ライブラリの net/http と Gorilla
- 自動生成するコードは、手書きコードとパッケージレベルで分離する。
- つまり、生成したコードは人の手でいじることなく、再生成が任意に実行できるように保つ
- 対応するURLをあとで変更できる
- リクエスト、レスポンスのオブジェクトに型つきでアクセスできる
- リクエストオブジェクトのバリデーションを行う
リクエスト、レスポンスオブジェクトの定義
リクエスト、レスポンスオブジェクトは単純に、json schemaのオブジェクト定義を、Goの構造体の定義に変換するだけです。
サンプルコード: test/gen/struct.go
ロジックの注入
APIロジック部分の実装は、自動生成されたコードにハンドラを登録する形にしました。ハンドラは次のような定義を生成しています。(サンプルでは、POST /hoge
というAPIを定義しています)
1 2 3 4 |
|
実装した関数を登録するには、次の関数を呼び出します。
1 2 3 4 5 |
|
サンプルコード詳細: test/gen/hoge.go
リクエストのバリデーション
JSON Schemaには、オブジェクトのバリデーションを記述する仕様が用意されています。 ( http://json-schema.org/latest/json-schema-validation.html ) こいつら、Goのバリデーションコードを生成します。
オブジェクトをバリデーションするのにちょうどいいライブラリがなかったため、この部分については、バリデーションを定義するためのライブラリを作りました。
特徴として
- バリデータの定義を使いまわせる
- ネストしたオブジェクトもバリデーションできる
- バリデータの中身はただの関数 (
func(interface{}) error
) - 構造体と、
map[string]interface{}
の両方をバリデーションできる
を備えています(実装がもうちょっと落ち着いたら別記事であげたいと思います)
バリデータとして、次のような感じで出力します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
サンプルコード: test/gen/validators.go
リクエストの中身(POSTなら r.Bodyに入っているJSON)を構造体に組み立た後、
1 2 3 4 |
|
のようにして、バリデーションしています。
サンプルコード: test/gen/hoge.go
リクエストだけから判断できるエラーについては、自動生成側でバリデーションしてしまうことで、ロジック側のコード量をかなり抑えることができます。
Goのコードを自動生成する時の細かいTips
Goのコードを生成するときには幾つかポイントがあります。
適当に出力して、 go fmt
構文エラーのチェックと、フォーマットを自動でしてくれます。 改行だけ注意してコードを吐き出せばよしなにやってくれます。
使わない可能性があるimportは、_に代入しておく
例えば、 regexp を使う場合と使わない場合があるコードを出力するとき、import して使用していないものがあるとコンパイルエラーになります。 ちゃんとフラグ立てて出力するか、goimport など使えば綺麗になりますが、
1 2 3 4 5 6 7 |
|
のように出力してしまえば良いです。
これは、 Google APIのGoクライアントのコード をみて参考にしました。
まとめ
JSON Hyper-Schema から、APIのドキュメンテーション、Goのソースコードを生成することで、かなり効率のよいAPIの開発ができるようになりました。
静的な型付け言語とコード自動生成の組み合わせは、生成 → コンパイル(型と実装のチェック) → 修正 というサイクルが高速で回せるため、とても強力です。
まだ取り組み始めたばかりなので、これからどんどん改善していきたいと思います!