Posted at
vte.cxDay 7

WebAPIではリソースとサービスの区別が重要

More than 1 year has passed since last update.

APIについては誰が設計しても同じものになるのが理想です。

今日はもう少し具体的に、APIの設計方法について述べようと思います。


RESTであれば悩まずに設計できる

APIといっても、単に画面の要求を受け付けるものから、データ交換のための汎用的なものまでいろいろあります。

設計方法も様々あるとは思いますが、私たちは基本的にRESTのアーキテクチャースタイルで設計することをお勧めしています。

RESTではリソースに対する操作が基本です。

RESTのアーキテクチャスタイルを守るということは、リソースに対してHTTP(s)のメソッドで操作するということです。

つまり、リソースに対してURIを定義し、それに対してGET/POST/PUT/DELETEを実行することでデータを操作します。

これは、直感的でわかりやすいアーキテクチャースタイルです。

画面とサービスを分けて開発するときに、相互運用性(インターオペラビリティ)が問題になることは既に述べたとおりですが、わかりやすいというのはインターオペラビリティ向上にとっても重要です。

また、RESTを前提にすれば、「適切な制約」を設けることで設計のコストが下がるという大きなメリットが生まれます。

リソース設計やらインターフェース設計やらで悩んでいたものが、自然と決定されていくため悩まなくてもよくなるのです。

このあたりの話については、RESTとJSON、スキーマ定義について思うところ で詳しく述べています。


REST設計の具体例

それでは、REST設計の具体例について説明しましょう。

まず、設計するのは、エンティティとエンドポイント(URI)です。

エンティティはシステム全体で統一的に扱うことのできるモデル(項目の集合)のことですが、具体的には、画面設計などで抽出した項目をスキーマとして記述していきます。詳しくは、【vte.cx入門】3.データをサーバに登録する:スキーマ定義編を参照してください。

エンティティを定義したら、次にエンドポイントURIを決めます。RESTでは、リソース(エンティティの形をしたデータ)のURIが一意になるように決定していきます。URIは、エンティティを元に階層構造を意識して命名していきます。

そのリソースを意味するような「名詞」がよいとされており、先の例であれば、/orderとか、/itemsなどが候補になるでしょう。個人的には名詞は必ずしも複数形である必要はないと思っています。

このように、RESTアーキテクチャスタイルであれば、あまり悩まなくて設計を進められます。


階層構造を意識するURI設計

RESTとJSON、スキーマ定義について思うところ でも述べていますが、vte.cxではURIの階層構造に意味を持たせています。

例えば、{リソースID1}/{リソースID2} は、ファイルシステムのフォルダのように、リソースID1の配下にID2があることを示しています。

/d/app/index.html とすれば、/appフォルダの下にindex.htmlが入っているというように直感的にわかります。

リソースのURI設計においても同じ考え方を適用し、階層管理することで悩まずに設計できるようになります。


サービスとリソースは区別すべき

実は前述した設計手法は、サービス(Provider)においては、うまくいかないことがわかっています。

サービスでは複数のリソースを加工して新たなリソースを生成します。

REST原理主義者のなかには、サービスが返すのもリソースとして定義すべきという人もいますが、私はそれはやりすぎだと思っています。

(一時期、私も同じ考えでしたが・・)

これについては、翻訳: WebAPI 設計のベストプラクティス でも言及されています。


一方で、REST の構造にマッチさせられないアクションもあると思います。例えば、複数のリソースを横断的に検索するようなアクションについては、特定リソースのエンドポイントに紐付けるのは、なんとも無理やりな感じがします。このような場合は、/search というエンドポイントを作ることで解決します。ちょっとルール違反な気もしますが、API 利用者から見ておかしくなく、混乱がないようにドキュメントにしっかりと書かれていれば問題ないのです。


サービスをリソースにマッピングすることがなぜ筋が悪いのか、重要なポイントなのでもう少し掘り下げます。

リソースが単なるデータとして扱えるなら混乱は起きませんし、マスターデータのようなスタティックなデータであればRESTがフィットします。

しかし、サービスはビジネスロジックなどの「処理」をまとめたものであり、データではありません。

サービスは、複数のリソースを参照し、何らかの「処理」を実行して新たなリソースを生成しますが、あくまで動的に結果を返す「処理」に主眼があります。

サービスのような動的なデータを返すものは、静的なリソース名ではなく、動的な振る舞いを表現する方がわかりやすいと思います。

例えば、登録処理や出力処理などでは、/register、/exportといった動詞の名前の方がしっくりきます。登録処理では、単なるデータ登録だけではなく、データをチェックしてエラーがあれば詳細を返します。出力処理ではパラメータによって様々な形式のデータを返します。

私はREST厨を自認していますが、原理主義者は嫌いです。

原理主義的な発想にとらわれると、サービスをリソースに無理やりマッピングしようとして苦労したり、本末転倒なことが起こりがちだからです。

サービスのURIがリソースっぽくないとか、どうでもいいのです!そんなこと。

重要なのは、繰り返しですが、インターオペラビリティと生産性です。

APIを利用する者にとってわかりやすいI/F設計にすべきです。


vte.cxにおける解決策

というわけで、結局、vte.cxではサービスとリソースを区別するようにしました。

vte.cxでは、リソースを返す純粋なREST APIと、サービスAPIの2つをもちます。

つまり、/d/foo など、/d で始まるものはリソースへのアクセスとなり、/s/barなど、/sで始まるものはサービスの実行になります。

/dは検索条件などで絞り込みができますが、基本的にリソースを返すだけなのでREST原理主義的な発想でも問題なく設計できます。また、前述したように/dでは階層構造にも意味があります。

一方の/sではどんなリソースが返るかというより、サーバサイドのJavaScriptでどんな処理を実行すべきかという観点で設計します。

ちなみに、サービス内部では複数のリソースを呼び出すことができますがサービスからサービスを呼びだすことはできません。

(正確にいうと、/sにマッピングされるのは1つのクラスでありそれしか実行できませんが、クラスは複数のメソッドを持つことができるため、内部において複数のメソッドを実行することは可能です。また、requireによって別のクラスを取り込むことができます。)

このように、vte.cxでは、自明なもの(リソース)とそうでないもの(サービス)に区別して設計を進めていきます。

リソースのように自明なもの、つまりエンティティやURIの設計は、画面の開発者が主導して進められます。

ただ、繰り返しになりますが、サービスI/Fの命名規則にREST原理主義を持ち込むべきではありません。サービスの振る舞いを意味するわかりやすい名前をつければそれで十分です。

自明でないサービスはビジネスロジックを含め、どのように設計していくかが課題となりますが、先日述べたように、Consumer-Driven Contract testingパターンなどを利用してクライアント主導で設計していくとよいと思います。

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