web-dev-qa-db-ja.com

RESTful JSON応答にメタデータを追加するためのベストプラクティスは何ですか?

背景

JSONとしてデータオブジェクトを返すRestful APIを構築しています。ほとんどの場合、データオブジェクトを返すだけで問題ありませんが、場合によってはf.ex.ページネーションまたは検証では、応答にメタデータを追加する必要があります。

これまでのところ

次の例のように、すべてのjson応答をラップしました。

{
    "metadata" :{
        "status": 200|500,
        "msg": "Some message here",
        "next": "http://api.domain.com/users/10/20"
        ...
    },
    "data" :{
        "id": 1001,
        "name": "Bob"
    }
}

長所

  • 役立つメタデータを応答に追加できます

短所

  • ほとんどの場合、メタデータフィールドは不要であり、json形式が複雑になります。
  • これはもはやデータオブジェクトではなく、エンベロープされた応答に似ているので、データオブジェクトを抽出せずにf.ex backbone.jsですぐに応答を使用することはできません。

質問

Jsonレスポンスにメタデータを追加するためのベストプラクティスは何ですか?

[〜#〜]更新[〜#〜]

以下の答えから私がこれまで得たもの:

  • metadata.statusを削除し、代わりにhttpプロトコルでhttp応答コードを返します(200、500 ...)
  • エラーメッセージをhttp 500応答の本文に追加
  • ページネーションの場合、ページネーション構造とその構造にネストされたデータを伝えるメタデータをいくつか持つのは当然です
  • 少量のメタデータをhttpヘッダーに追加できます(X-something)
42
user920041

RESTful APIでメタデータを渡す方法はいくつかあります。

  1. HTTPステータスコード
  2. ヘッダー
  3. レスポンスボディ

Metadata.statusには、HTTPステータスコードを使用してください。メタデータが応答全体を参照している場合は、ヘッダーフィールドとして追加できます。メタデータが応答の一部のみを参照する場合は、オブジェクトの一部としてメタデータを埋め込む必要があります。DO N'T応答全体を人工的なエンベロープでラップし、ラッパーをデータとメタデータに分割します。

そして最後にAPI全体で一貫性がある選択した内容を使用します。

良い例は、ページネーション付きのコレクション全体に対するGETです。 GET/itemsカスタムヘッダーでコレクションのサイズと現在のページを返すことができます。標準のリンクヘッダーのページネーションリンク:

Link: <https://api.mydomain.com/v1/items?limit=25&offset=25>; rel=next

このアプローチの問題は、応答の特定の要素を参照するメタデータを追加する必要がある場合です。その場合は、オブジェクト自体に埋め込むだけです。また、一貫したアプローチをとるために、すべてのメタデータを常に応答に追加します。 GET/itemsに戻って、各アイテムがメタデータを作成および更新したと想像してください。

{
  items:[
    {
      "id":"w67e87898dnkwu4752igd",
      "message" : "some content",
      "_created": "2014-02-14T10:07:39.574Z",
      "_updated": "2014-02-14T10:07:39.574Z"
    },
    ......
    {
      "id":"asjdfiu3748hiuqdh",
      "message" : "some other content",
      "_created": "2014-02-14T10:07:39.574Z",
      "_updated": "2014-02-14T10:07:39.574Z"
    }
  ],
  "_total" :133,
  "_links" :[
     {
        "next" :{
           href : "https://api.mydomain.com/v1/items?limit=25&offset=25"
         } 
   ]
}

コレクションレスポンスは特殊なケースであることに注意してください。コレクションにメタデータを追加すると、コレクションは配列として返されなくなり、配列を含むオブジェクトである必要があります。なぜオブジェクトなのか?メタデータ属性を追加したいからです。

個々のアイテムのメタデータと比較します。エンティティのラッピングに近いものはありません。リソースにいくつかの属性を追加するだけです。

1つの規則は、コントロールまたはメタデータフィールドを区別することです。これらのフィールドの先頭にアンダースコアを付けることができます。

10
Daniel Cerecedo

@Charlieのコメントの行に沿って:質問のページ分割の部分では、メタデータを応答somhowにベイクする必要がありますが、ここでのstatusおよびmessage属性は多少冗長です。 HTTPプロトコル自体(ステータス200-モデルが見つかりました、404-モデルが見つかりません、403-不十分なプライバシー、アイデアがわかります)( spec を参照)。サーバーがエラー状態を返したとしても、message部分を応答本文として送信できます。これら2つのフィールドは、メタデータのニーズのかなりの部分をカバーします。

個人的に、私は(ab)のメタデータの小さな部分にカスタムHTTPヘッダーを使用する傾向があります(X-接頭辞)、しかし、それが実用的でなくなる制限はかなり低いと思います。

私は expanded スコープが小さい質問でこれについて少し説明しましたが、この質問ではポイントはまだ有効だと思います。

4
Jacob Oscarson

このページを読むことをお勧めします https://www.odata.org/ ODataを使用する必要はありませんが、ODataが動作する方法は、RESTの優れたプラクティスの良い例です。

1

同じ使用例があり、JSON応答にページ分割メタデータを追加する必要がありました。このデータを処理できるコレクションタイプと、Rails側の軽量ラッパーを作成しました。この例では、ビューで参照できるようにコレクションデータにメタデータを追加しています。

そこで、次のようなバックボーンコレクションクラスを作成しました

// Example response:
// { num_pages: 4, limit_value: 25, current_page: 1, total_count: 97
//   records: [{...}, {...}] }

PageableCollection = Backbone.Collection.extend({
  parse: function(resp, xhr)  {
    this.numPages = resp.num_pages;
    this.limitValue = resp.limit_value;
    this.currentPage = resp.current_page;
    this.totalCount = resp.total_count;
    return resp.records;
  }  
});

次に、この単純なクラスをRails側に作成し、Kaminariでページ付けされたときにメタデータを出力します

class PageableCollection
  def initialize (collection)
    @collection = collection
  end
  def as_json(opts = {})
    {
      :num_pages => @collection.num_pages 
      :limit_value => @collection.limit_value 
      :current_page => @collection.current_page,
      :total_count => @collection.total_count
      :records => @collection.to_a.as_json(opts)
    }
  end
end

このようなコントローラーで使用します

class ThingsController < ApplicationController
  def index 
    @things = Thing.all.page params[:page]
    render :json => PageableCollection.new(@things)
  end
end

楽しい。お役に立てば幸いです。

0
maxl0rd