JSON Schemaを上手く運用出来そうなprmdとその周りのお話

  • 219
    Like
  • 0
    Comment
More than 1 year has passed since last update.

最近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ドキュメントを生成するdocrender
があります。

READMEの流れを追っていけば、簡単にAPIドキュメント生成が出来ます。

Validationはどう行うか?

ところが、困ったことに今度はJSON Schemaファイルを作成できたのに
バリデーションに一手間が必要になります。
prmd init app > schemata/app.json prmd init user > schemata/user.jsonでschemataファイルを作成して
prmd combineの場合、作成されるschema.jsonのpropertiesの値は以下のようになります。

schema.json(前後省略)
{
  "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を上のものに合わせる場合、
以下のような方法が考えられると思います。

  1. 仕様を合わせる
  2. combine時に使うmeta.jsonに設定をする
  3. schema.jsonを動的に修正する

1.は仕様を上のようなリソース名をレスポンスに含めるというもの。
新規ではじめるならば、アリかもしれません。

2.は、combine時のmetaオプションで指定するmeta.jsonに
propertiesを入れます。

たとえば、instagramのAPIのフォーマットである{"meta":{"code":200},"data":null}にしたい場合

meta.json
{
  "properties" : {
    "meta": {
      "$ref": "#/definitions/meta"
    },
    "data": {
      "type": "object",
      "anyOf": [
        {"$ref": "#/definitions/user"},
        {"$ref": "#/definitions/media"}
      ]
    }
  }
}

とフォーマットを合わせたりします。
(propertiesは上書きではなく、マージなので不必要なプロパティが残るので、なんらかの対応する必要があります。

3.は例えば上の場合、anyOfがどれか1つ以上のフォーマットにマッチしていればOKというものなので、厳密に行うにはバリデーションチェックの前にanyOfの内容を書き換えたりするというものです。

どれも最適解ではない気がしますが、
ここら辺をいじると各APIのリソースのバリデーションが出来るようになります。

ただ、これでもlinkの違いを自動的に判別してテストはしてくれないので
それなりのコードは書くことになります。

まとめ

なんかすごい長めにややこしいこと書いてましたが、
時系列には、こんなことになってました。

  1. API Blueprintだと、スペルミスとか自動的にチェックしきれなくてつらい 
  2. JSON Schemaからだと、バリデーションのチェックが出来そう
  3. 細かいリクエストのスキーマの調整が面倒すぎるし、linksに対応したバリデーションはある程度自分で書かなければいけなさそう

いまだに、ベストな方法が調べきれていないのですが
なんか当たり前な方法があったりするような気がしないでもなく、
みんなどうやってるんだろ、って感じです。

参考

関連