最近APIのバリデーションを行うのにJSON Schemaを使おうという話をよく聞くのですが
じゃあやってみようとすると、単体のJSONのバリデーションを行うことは出来るけれど
それが実際の運用に適用できるようにするためには、
それなりに考えなくてはいけないようです。
JSON Schemaって?
JSON Schemaは、文字の通りJSONのスキーマ定義を明確にするものです。
2014/09/07現在、IETFにdraft v4が3つに分割されて公開されています。
それで、単体のJSONファイルの確認ならばCoreだけで十分なのですが
WebサービスのAPIを管理しようとなると、
JSON Hyper-Schemaのほうで定義されているlinks
を使って、ひとつのURIに対して複数の説明をつけたりすることになります。
実運用で必要なこと
そもそも、APIサーバーを運用するのに当たって
**【APIドキュメントを作成する】**というのが必須になってきます。
前に書いたAPI Blueprintの場合は
Markdown記法でドキュメントを記載することができます。
しかも、API Blueprintの場合は、中央データとしてのJSONを作成し、
そこからモックサーバーを立ち上げたり、
RestクライアントのPostmanに
インポートするためのJSONを作成を作成するライブラリが用意されてたりします。
APIドキュメントと実際のコードの差異をなくす必要がある
ところが、そのAPI BluePrintの場合、
レスポンスフォーマットがいわゆるMarkdownのcode(バッククォートで書くやつ)で
レスポンス例を書くだけなので、単体ではバリデーションのテストが出来なかったりします。
レスポンスの検証が的確に出来ないと何に困るかというと
ドキュメントに書かれた項目がサーバーに正しく実装されていない可能性が出てきてしまい
ドキュメントに書いてある項目が実際に返ってこなかったり、
逆に返ってこないはずのものが返ってきてしまったりします。
そのために、単純にエクセルなどでドキュメントを作成した場合に
仕様変更があった場合に、そのドキュメントの整合性を保つのに
ものすごい労力を使ったりします。
なので、簡単に**【JSONのバリデーションを行えるようにする】**のが必要になってきます。
JSON Schemaの場合、もともとがJSONの構文チェックを行うのが目的なので
Blueprintの比べて、バリデーションの機能が豊富なので、
バリデーションチェックの問題に対応しやすくなります。
すぐに大規模化するSchemaファイル
ところがWebサーバーの返すAPIの形式はURIごとに存在します。
これをすべてJSON Schemaで定義しようとすると、URI
× 利用するメソッド
ごとに
用意しなければいけなくなります。さすがにそれはしんどい。
そこでJSON Hyper-Schemaを使うことになります。
例えば、JSON Hyper-Schemaにある"links"を使う場合
URIやリクエストメソッドによって、リクエストの値やレスポンスの結果を変えることが出来ます。
ただ、そうなるとウェブサービスの規模が大きくなると
schema.jsonの管理がどんどん複雑になっていきます。
そこでJSON Schema管理ツールであるprmdの出番になります。
prmd
使い方は prmdのREADMEを参照するのが一番手っ取り早いので、ここでは省略。
ざっくり利用する流れを書くと
リソースごとにyaml、もしくはjsonファイルでScaffoldを作ってくれるinit
。
リソースごとのファイルを1つのJSON Schemaにマージすることが出来るcombine
。
JSON Schemaを検証するverify
JSON SchemaからAPIドキュメントを生成するdoc
とrender
があります。
READMEの流れを追っていけば、簡単にAPIドキュメント生成が出来ます。
Validationはどう行うか?
ところが、困ったことに今度はJSON Schemaファイルを作成できたのに
バリデーションに一手間が必要になります。
prmd init app > schemata/app.json
prmd init user > schemata/user.json
でschemataファイルを作成して
prmd combine
の場合、作成されるschema.jsonのpropertiesの値は以下のようになります。
{
"properties": {
"app": {
"$ref": "#/definitions/app"
},
"user": {
"$ref": "#/definitions/user"
}
}
}
このschema.jsonでは、以下のようなJSONのみをチェックすることが出来ます。
{
"app": {
"id": "01234567-89ab-cdef-0123-456789abcdef",
"created_at": "2012-01-01T12:00:00",
"updated_at": "2012-01-01T12:00:00"
}
}
以下の場合、idがuuidのフォーマットじゃないよとエラーになります。
{
"app": {
"id": 32929,
"created_at": "2012-01-01T12:00:00Z",
"updated_at": "2012-01-01T12:00:00Z"
}
}
つまり、prmdを使ってschema.jsonを作った場合,properties
を調整する必要があります。
propertiesを調整する
propertiesを上のものに合わせる場合、
以下のような方法が考えられると思います。
- 仕様を合わせる
- combine時に使うmeta.jsonに設定をする
- schema.jsonを動的に修正する
1.は仕様を上のようなリソース名をレスポンスに含めるというもの。
新規ではじめるならば、アリかもしれません。
2.は、combine
時のmeta
オプションで指定するmeta.jsonに
propertiesを入れます。
たとえば、instagramのAPIのフォーマットである{"meta":{"code":200},"data":null}
にしたい場合
{
"properties" : {
"meta": {
"$ref": "#/definitions/meta"
},
"data": {
"type": "object",
"anyOf": [
{"$ref": "#/definitions/user"},
{"$ref": "#/definitions/media"}
]
}
}
}
とフォーマットを合わせたりします。
(propertiesは上書きではなく、マージなので不必要なプロパティが残るので、なんらかの対応する必要があります。
3.は例えば上の場合、anyOf
がどれか1つ以上のフォーマットにマッチしていればOKというものなので、厳密に行うにはバリデーションチェックの前にanyOf
の内容を書き換えたりするというものです。
どれも最適解ではない気がしますが、
ここら辺をいじると各APIのリソースのバリデーションが出来るようになります。
ただ、これでもlinkの違いを自動的に判別してテストはしてくれないので
それなりのコードは書くことになります。
まとめ
なんかすごい長めにややこしいこと書いてましたが、
時系列には、こんなことになってました。
- API Blueprintだと、スペルミスとか自動的にチェックしきれなくてつらい
- JSON Schemaからだと、バリデーションのチェックが出来そう
- 細かいリクエストのスキーマの調整が面倒すぎるし、linksに対応したバリデーションはある程度自分で書かなければいけなさそう
いまだに、ベストな方法が調べきれていないのですが
なんか当たり前な方法があったりするような気がしないでもなく、
みんなどうやってるんだろ、って感じです。