Posted at
vte.cxDay 8

WebAPI 設計のベストプラクティス ~ vte.cx版 ~

More than 1 year has passed since last update.

昨日紹介した、翻訳: WebAPI 設計のベストプラクティス は、実はvte.cxのベストプラクティスとは相当な違いがあります。というより、紹介した例以外はほとんど違います。

今日はこの違いについて説明したいと思いますが、私の主張が100%正しいというつもりはありません。

あくまで、RESTの一つの実装例という捉え方をしていただければ幸いです。


vte.cxにおけるRESTの考え方

vte.cxでは、リソースの最小単位はEntryになります。1つのEntryには複数の項目が入ります。

これはRDBでいうところのレコードに相当します。

リソースのエンドポイントURLは/dから始まります。

例えば、以下のURLに?eパラメータをつけてGET実行するとorderエントリそのものが返ります。

GET /d/order?e : orderエントリそのものを返す

また、?fパラメータを付与すると配下のEntryの集合、つまり、Feedを返します。(Entryを複数件まとめた配列をFeedといいます)

GET /d/order?f : orderエントリ配下のデータ(Feed)を返す

これは、orderテーブルの検索(select * from order)に相当します。

また、以下のようにパラメータに検索条件を付けることもできます。

GET /d/order?f&updated=2016* : orderエントリ配下のデータ(Feed)のうち、2016年に更新されたものを返す

vte.cxでは、/orderのように配下に複数のエントリを持つものを便宜上フォルダと呼んでいます。

これはWindowsのフォルダと同じようなものですが、実体はEntryであり、フォルダとエントリの区別はありません。

また、以下のように配下のエントリを追加していくことで多段的な階層構造にすることもできます。

GET /d/order/items/messages/・・

vte.cxのリソース管理の詳細は、ドキュメント を参照してください。


登録更新におけるエンドポイントURIの考え方

登録・更新では複数件を対象にしたいことがありますが、その場合、エンドポイントURLの指定方法が問題になります。

複数のentryを1回のPOST/PUTで登録更新するには1つのエンドポイントURLだけでは不十分です。

vte.cxでは、URIに/d/orderを指定するかわりに、entryの1項目であるlink.___hrefをキーとして参照することで、それぞれが異なるキーを指定できるようにしています。一方で、エンドポイントURLが/dだけになるため、一見、RESTのアーキテクチャスタイルに反するように見えますが、各エントリはユニークなキー(URL)にマッピングされるため基本的には問題ありません。

POST|PUT /d

{
"feed": {
"entry": [
{
"favorite": {
"food": "りんご"
},
"link": [
{
"___href": "/order/1",
"___rel": "self"
}
]
}
]
}
}


PATCHは必要か?

部分更新ということでPATCHメソッドが使われることが多いようですが、これは本当に必要でしょうか?

KISSの原則とは、"Keep it short and simple" (簡潔に単純にしておけ)という経験的な原則のことです。

この原則に則るのであれば、PUTだけにすべきところです。

実は、vte.cxにおけるPUT更新は部分更新です。

これは、第一階層の項目に値が代入されているものを丸ごと置き換えますが、値が代入されていない項目については更新しません。(第二階層以下の項目が省略されている場合は空白で上書きになります。)

また、Entryのすべての項目を更新したい場合はすべての値を代入すればよく、PUTメソッドだけで十分であることがわかります。

(部分更新なので、必要なのはPUTではなくPATCHメソッドなのかもしれませんが・・)

詳しくは、ドキュメントを参照してください。


バージョン番号をURLに含めるべきではない

ブラウザからバージョンを変えてリクエストをすることを考えるとURL に含めるのが妥当という人がいます。

しかし、なぜリクエストパラメータに入れることを考えないのでしょうか?

/v2/order と、/order?v2 とでは、後者の方がより妥当に見えるのは私だけではないでしょう。

バージョン番号をURIに含めるべきでない理由については、You need NOT API version 2 で詳しく述べています。


レスポンスのフィールドは絞れるようにすべきか?

リソースの最小単位はEntryであると説明しました。これ以上にレスポンスのフィールドを細かく取得する必要はないように思います。

たしかに、項目単位に絞ることで通信量は削減できるかもしれませんが、絞り込み項目を一つ一つ細かくリクエストに指定する必要があります。

レスポンスのフィードを絞れるようにするということは、リソースの最小単位がEntryではなく、項目単位になることを意味します。

そもそも、リソースを必要最小限の単位でまとめたものがEntryのはずであり、それさえも大きすぎて細かくしたいということであればモデリングがまずい気がします。レスポンスを含めて適切なEntryになるように設計を見直しましょう。


可能な限りJSONで返すべきか

JSONはJavaScriptとの相性がよく、開発のし易さという意味で優れたフォーマットだといえます。

しかし、JSONは整形しないと見れたものじゃありませんし、可読性という点ではXMLの方が優れていると思います。

そもそも、ブラウザで直接JSONを表示するのはセキュリティ上好ましくないため、vte.cxではXHR通信以外でJSONを返すことを禁止しているぐらいです。

XMLであれば安全にブラウザ表示できるので普段はデータ確認をXMLで行っています。標準的なブラウザであればXMLを整形して表示してくれます。JSON を整形するのではなく素直にXMLを使いましょう。(というか、同じリソースをJSONでもXMLでも表現できるサービスを作るのが大変なのでしょう?)

また、データ転送効率はJSONよりMessagepackの方がはるかに優れています。

vte.cxでは、大量のデータ通信が伴うAPIではMessagepackを使うことを推奨しています。

このように、基本はJSONを使うとしても、XMLやMessagepackなども必要に応じて使い分けるのがよいと思います。

そもそも、フォーマットはリソースを表現するためにあるものなので、何を使っても構いませんし、JSON vs XMLという構図自体がおかしいと思います。


フィールドの命名規則について

キャメルケースよりスネークケースの方がいいとか、どっちでもいいんじゃないですか。


要素はラップせずに返そう

好みの問題だと思います。

vte.cxでは、feedやentryでラップしています。


HTTPヘッダの活用かメタデータの導入か

ページング情報をレスポンスヘッダに入れたり、リクエスト制限情報をレスポンスヘッダに入れたりするのは、HTTPヘッダを活用することでJSON本体にはメタ情報や制御情報などを入れたくないということだと思います。

しかし、HTTPヘッダをむやみに拡張していくのは複雑になってインターオペラビリティに影響を与えます。

個人的には、JSON本体にメタデータを導入することをお勧めします。

vte.cxではATOM Feedを採用しています。


関連データを埋め込む手段を作ろう

これはサービスの役割ではないでしょうか?

サービスは、複数の関連データを参照して新しいエンティティを生成します。

その際にビジネスロジックも必要になるはずです。

単なる関連データの埋め込み機能で十分とは思えません。


トークン認証を使おう

素直にセッションを使いましょう。

Webアプリケーションでステートレスにするのは現実的ではありません。

このあたりの話は、RESTに関する3つの間違いで詳しく説明しています。

それでは、また明日。:relaxed: