API設計について

前回までのOpenAPIの話の流れでAPI設計について整理してみたいと思います。

REST (RESTful)

Webアプリケーション上でAPIを設計する際、ほぼ必ずと言っていいほどREST APIについての説明がセットになると思います。

RESTとはREpresentational State Transferの略であり、以下の原則に則ったWebアプリケーションの設計思想です。

  • 統一インターフェース:情報をやり取りする方法が統一されている
  • アドレス可能性:各情報にアクセスするためのURIが一意に定められている
  • 接続性:情報の内部に別の情報へのリンクを含めることができる
  • ステートレス:状態管理等を行わず、都度情報のやり取りが完結する

例えば/usersというエンドポイントに対してGET / POST / PUT / DELETEのHTTPメソッドでアクセスすることで以下のような処理をそれぞれ行うことができます。

  • GET: ユーザー情報の取得
  • POST: ユーザー情報の作成
  • PUT: ユーザー情報の更新
  • DELETE: ユーザー情報の削除

これは/usersというエンドポイントがユーザーというリソースにアクセスするURIとして一意に定められているからであり、またこのAPIではHTTPメソッドを用いてやり取りすることが定められているからです。

このようなAPIをREST (RESTful) APIと呼びます。

リソース

↑でリソースという話が出てきましたが、「何をどうする」の「何を」に当たるのがAPIにおけるリソースかと思います。

REST APIにおいてはリソースをどう設定するかが重要ではないかと思います。

例えば上の例で言うとユーザー情報に何を含むのか。id、名前、性別、メールアドレス、電話番号etc...

これはドメイン駆動設計におけるエンティティに近いのかなと思っています。

どういうドメインでどういうことを実現したいのか。それに必要なオブジェクトは何なのか。

これらが先にあって、そこに対してアクセスするためのエンドポイントを作成してHTTPメソッドを割り当てる、というイメージだと思います。

もちろんそのためにはしっかりしたDB設計も必要になると思います。

エンドポイント

エンドポイントも簡潔でわかりやすいものにする必要があります。

例えばユーザーIDを指定してそのユーザーの経歴を取得するエンドポイントが

GET /u/{id}/c

だと何を取得できるのか一目見ただけではわかりません。

GET /users/{id}/career

というURIだとわかりやすいと思います。

また階層構造にも気をつけなければなりません。

例えば製品一覧の中からIDを指定して特定のカバンの情報を取得したい場合に、

GET /bags/{id}/products

では階層がめちゃくちゃです。通常は製品が上位、カバンが下位のカテゴリとなっているので、

GET /products/bags/{id}

となります。

URI 詳細
/products 製品一覧
/products/bags 製品一覧の中のカバン一覧
/products/bags/1 idが1のカバン
/products/bags/1/reviews idが1のカバンのレビュー

このように階層構造を意識したURIにすることでより意味が伝わりやすくなります。

パラメータ

パラメータの指定方法も考慮すべき点です。

GETの場合だとクエリパラメータとパスパラメータ両方使えますが、基本的には以下のように使い分けるのが良いのではと思います。

種類 用途
クエリパラメータ 任意のパラメータ
パスパラメータ 必須のパラメータ

例えば蔵書一覧を取得するGET /booksというエンドポイントがある場合、漫画に該当するものだけを取得する場合はGET /books?category=comicsのようになるでしょう。

一方で、GET /books/comicsというエンドポイントも考えられます。が、あまり現実的ではないように思います。

/books/comicsがあるならば/books/novels/books/nonfictions等、各カテゴリごとにエンドポイントを設定することになるからです。

その為、柔軟性を持たせる意味でもカテゴリ等については任意のパラメータとする方が無難かと思います。

逆にGET /books/{id}等、特定のリソースに対するエンドポイントはパスパラメータとした方が良いと思います。

まとめ

以上API設計について基本的なところを整理してみました。

一度APIを作ってしまうと後からエンドポイント等を変更するのは容易ではないので個人的にも難しさを感じている箇所です。

引き続き勉強していきたいと思います。