Web APIを作るとき、JSONのデータ構造をどうするか悩んだことはありますか?
{
"id": 3342124,
"message": "Hi!",
"user": {
"id": 3456,
"name": "Taro Yamada",
"image_url": "/images/taro.png"
}
}
フラットにする
{
"id": 3342124,
"message": "Hi!",
"user_id": 3456,
"user_name": "Taro Yamada",
"user_image_url": "/images/taro.png"
}
エンベロープでデータ全体を包む
{
"response": {
"id": 3342124,
"message": "Hi!",
"user": {
"id": 3456,
"name": "Taro Yamada",
"image_url": "/images/taro.png"
}
}
}
など、どの構造がいいでしょうか? もっと違う構造も考えられます。
JSONはシンプルですが、構造に制約がなさすぎます。適切な設計を行うには適切な制約が必要です。
そこで、plain JSONに少し制約を加えたJSONベースのフォーマットを使うことをおすすめします。
もしあなたが、JSONレスポンスをどのようなフォーマットにするかをチームで議論したことがあるなら、JSON APIは『自転車置き場の議論』に対抗する武器となる。
共有された規約に従うことで、生産性が向上し、汎用的なツールを利用でき、アプリケーションという重要なものに集中することができる。
— http://jsonapi.org/ より
制約の弱いもの、強いもの、さまざまなフォーマットが存在しますが、今回は比較的制約の強いフォーマットを紹介します。
HAL
Content-Type: application/hal+json
{
"_links": {
"self": { "href": "/messages/3342124" }
},
"id": 3342124,
"message": "Hi!",
"_embedded": {
"user": {
"_links": {
"self": { "href": "/users/3456" }
},
"id": 3456,
"name": "Taro Yamada",
"image_url": "/images/taro.png"
}
}
}
通常のオブジェクト(リソースと呼ぶ)に _links
_embedded
というプロパティを加えたものになっています。この _embedded
の中にさらにリソースを入れられる(埋め込みリソース)ようになっているので、ただネストするよりも、複数のリソース間の関係がはっきりします。
HALは現在インターネットドラフトになっています。また、最近AWSのAPIでは積極的にHALが採用されています。
例: http://docs.aws.amazon.com/apigateway/api-reference/
JSON API
Content-Type: application/vnd.api+json
紛らわしいですが、“JSON API”というフォーマットの名前です。
{
"links": {
"self": "/messages/3342124"
},
"data": {
"type": "messages",
"id": 3342124,
"attributes": {
"message": "Hi!"
},
"relationships": {
"user": {
"data": {
"type": "users",
"id": 3456
}
}
}
},
"included": [{
"links": {
"self": { "href": "/users/3456" }
},
"type": "users",
"id": 3456,
"attributes": {
"name": "Taro Yamada",
"image_url": "/images/taro.png"
}
}]
}
JSON APIでは、従来のデータ(キー・値)は data
の中の attributes
に入ります。それに加えて、リソースを識別するために type
と id
を使用します。
リソース間の関係を記述するため、特別に relationships
が用意されています。また、埋め込みリソースは included
で可能です。
さらに、JSON APIの特徴として、以下のようにかなり仕様が豊富です。
- GETだけではなく、POST/PATCH/DELETE(いわゆるCRUD操作)の仕様を定義している
- 特定のフィールドのみを指定したり、コレクションのsort、page、filterを行うURLパラメータの仕様を定義している
- エラー時のフォーマットを定義している
Collection+JSON
Content-Type: application/vnd.collection+json
{
"collection": {
"version": "1.0",
"href": "/messages",
"items": [{
"href": "/messages/3342124",
"data": [
{ "name": "id", "value": 3342124 },
{ "name": "message", "value": "Hi!" }
],
"links": [
{ "rel": "user", "href": "/users/3456" }
]
}],
"template": [{
"data": [
{ "name": "message", "value": "", "prompt": "Enter message" }
]
}],
"queries": [{
"rel": "search",
"href": "/messages/search",
"prompt": "Search messages",
"data": [
{ "name": "q", "value": "" }
]
}]
}
}
Collection+JSONは基本的にコレクション(複数のリソースをまとめたもの)を扱うフォーマットです。データは collection
の中の items
に配列として入ります。配列の中身を1つだけにすることで単独のリソースも表すことができます。
埋め込みリソースは標準では用意されていません(拡張仕様で可能)。
Collection+JSONの一番の特徴は template
queries
です。これはHTMLのフォームのようにリクエスト時のパラメータを指定します。 template
はリソースに対するPOST/PUT/DELETE操作のとき、 queries
はそれ以外の検索などの操作に使用します。クライアントはこの記述に従うことでサーバに正しいクエリのリクエストを送信することができます。
また、エラー時のフォーマットも定義されています。
フォーマットを使うメリット
- データ構造の設計に悩まなくてよくなる
- クライアント・サーバの実装にそのフォーマット用のライブラリを利用できる
- フォーマットの仕様がデータ構造(場合によってリクエストパラメータなども)の仕様になるので、その部分についてはドキュメントを書く必要がなくなる
うまくライブラリを使えば、作業が大幅に省力化できるでしょう。
JSONベースのフォーマット自体がまだマイナーなので、必要なプラットフォームのライブラリがまだないかもしれませんが、汎用的な仕様なので、一度ライブラリを書けば以後も再利用することができます。
今回紹介したものはWeb APIのための汎用的なフォーマットなので、Web APIに特化したさまざまな仕様が用意されています。ぜひ採用を検討してみてはいかがでしょうか。
参考:ライブラリのリンク集
- HAL
https://github.com/mikekelly/hal_specification/wiki/Libraries - JSON API
http://jsonapi.org/implementations/ - Collection+JSON
(リンク集なし? 検索すると多く見つかる)