はじめに
初めてRESTを使う機会がありRESTFulですか?
という質問があがったが正直よく分からなかった。
今後議論できるように
RESTFulについて、
「RESTFulではない」とはどういうことか批判的アプローチをしたいと思う。
「RESTFulではない」7つの理由
- リソース指向でない
- HTTPメソッドの誤用
- ステートフル
- 統一インターフェースの欠如
- リソースの表現が不適切
- 自己記述的メッセージの欠如
- HATEOASの欠如
1. リソース指向でない
エンドポイントがリソースではなく、動詞やアクションを表現している。
/create_user: #エンドポイント名【Path Item Object】
POST: # HTTPメソッド
summary: ユーザを作成する
↑問題点: エンドポイントが動詞を使用しておりリソース指向でない。
/user: #エンドポイント名【Path Item Object】
POST: # HTTPメソッド
summary: ユーザを作成する
↑エンドポイント名(名詞)とHTTPメソッド(動詞)を組み合わせて表現する。
userをPOSTする。
リソース = 資源・資産(= 物)を意味するので名詞になる。
2. HTTPメソッドの誤用
HTTPメソッドを適切に使用していない。
/user: #エンドポイント名【Path Item Object】
GET: # HTTPメソッド
summary: ユーザを作成する
↑問題点: リソースを作成するエンドポイントでGETを指定している。
/user: #エンドポイント名【Path Item Object】
POST: # HTTPメソッド
summary: ユーザを作成する
↑エンドポイントの動作に沿ったHTTPメソッドを使用する。
userをPOSTする。
エンドポイントの振る舞いに合ったHTTPメソッド。
3. ステートフル
サーバーがクライアントの状態を保持している
/login: #エンドポイント名【Path Item Object】
POST: # HTTPメソッド
summary: サーバ側でログインを行う
/user: # エンドポイント名【Path Item Object】
post: # HTTPメソッド
summary: ユーザを作成する
parameters:
- name: sessionId
in: query
required: true
schema:
type: string
description: セッションID
↑問題点: RESTではステートレス(状態を持たない)であるべきだが、
サーバーがセッションを保持して、クライアントの状態を管理している。
/user: # エンドポイント名【Path Item Object】
post: # HTTPメソッド
summary: ユーザを作成する
parameters:
- name: sessionId
in: header
required: true
schema:
type: string
description: セッションID
↑ログイン状態を確認するHTTPヘッダーとして渡す。
例1はログイン状態をサーバーで把握することは不適切。
例2はメタ情報として渡すがqueryではなくheaderとして渡すことで
表面的にはRESTFulになる(実際にはサーバー側でセッション状態を保持しているため、本質的にはステートフル)。
4. 統一インターフェースの欠如
/products: # エンドポイント名【Path Item Object】
get:
summary: 商品のリストを取得するエンドポイント
post:
summary: 新しい商品を作成するエンドポイント
/products/{id}: # エンドポイント名【Path Item Object】
get:
summary: 特定の商品を取得するエンドポイント
put:
summary: 特定の商品を更新するエンドポイント
/products/{id}/archive: # エンドポイント名【Path Item Object】
post:
summary: 特定の商品をアーカイブするエンドポイント
↑問題点: エンドポイントの考え方に一貫性がない。
/products: # エンドポイント名【Path Item Object】
get:
summary: 商品のリストを取得するエンドポイント
post:
summary: 新しい商品を作成するエンドポイント
/products/{id}: # エンドポイント名【Path Item Object】
get:
summary: 特定の商品を取得するエンドポイント
put:
summary: 特定の商品を更新するエンドポイント
patch:
summary: 特定の商品を部分的に更新するエンドポイント(アーカイブなど)
↑商品のアーカイブエンドポイントを/products/{id}のPATCHに変更して一貫性を持たせる。
5. リソースの表現が不適切
/users: # エンドポイント名【Path Item Object】
get:
summary: ユーザのリストを取得する
responses:
'200':
description: ユーザのリストを取得しました
content:
application/xml: # 不適切なデータフォーマット
schema:
type: array
items:
type: object
properties:
id:
type: integer
name:
type: string
↑問題点: レスポンスの形式が推奨された形式になっていない。
/users: # エンドポイント名【Path Item Object】
get:
summary: ユーザのリストを取得する
responses:
'200':
description: ユーザのリストを取得しました
content:
application/json: # 適切なデータフォーマット
schema:
type: array
items:
type: object
properties:
id:
type: integer
name:
type: string
↑レスポンスの形式を推奨されているapplication/jsonに変更してAPIの互換性を高める。
6. 自己記述的メッセージの欠如
自己記述的メッセージ(Self-descriptive Messages):
メッセージ自体にその内容を理解するために必要な情報が含まれていることを指す。
必要な情報が含まれていることで、
クライアントやサーバーがメッセージの内容を解釈するのに
追加のコンテキストや外部の情報を必要としない。
自己記述的メッセージの欠如例
- コメント(summary, description)が書かれていない、誤っている、または内容が不十分
- レスポンスとして呼び出し元に必要な情報(データ・エラー)が不足している
- ステータスコードが適切に使用されていない
- 例: リソースが見つからない場合に
404 Not Found
を返さず、200 OK
を返す
- 例: リソースが見つからない場合に
- 必要なヘッダー情報が含まれていない
- リクエストやレスポンスのフォーマットが一貫していない
自己記述的メッセージの非欠如例
- 分かりやすいコメント(summary, description)が書かれている
- レスポンスとして呼び出し元に必要な情報(データ・エラー)を返される
- 適切なステータスコードが使用されている
- 必要なヘッダー情報が含まれている
- リクエストやレスポンスのフォーマットが一貫している
7. HATEOASの欠如
HATEOAS(Hypermedia as the Engine of Application State):
APIのレスポンスにハイパーメディアリンクを含めることで、クライアントが次にどのエンドポイントにアクセスすべきかを示す設計パターン。
/user:
post:
summary: 新しいユーザを作成するエンドポイント
responses:
'201':
description: ユーザが正常に作成されました
content:
application/json:
schema:
type: object
properties:
user_name:
type: string
description: ユーザの名前
↑問題点: HATEOASが記載されていない。
/user:
post:
summary: 新しいユーザを作成するエンドポイント
responses:
'201':
description: ユーザが正常に作成されました
content:
application/json:
schema:
type: object
properties:
user_name:
type: string
description: ユーザの名前
links:
type: array
items:
type: object
properties:
rel:
type: string
description: リンクの関係性
href:
type: string
description: リンク先のURL
method:
type: string
description: HTTPメソッド
required:
- rel
- href
- method
↑HATEOASとしてlinksを記載する
所感
個人の想像ですが、現実的にRESTFulを守れているか否かで考えると以下の整理になります。
- リソース指向でない
- 基本的に起こらないが、設計の初期段階や経験不足のチームでは起こり得る
- HTTPメソッドの誤用
- プロジェクトの特性で起こることがあり、使用するHTTPを制限し意図して本来と異なるHTTPメソッドを使用することがある
- ステートフル
- プロジェクトの特性で起こることがあり、技術や業務的な制約に起因してステートフルになることがある
- 統一インターフェースの欠如
- 基本的に起こらないが、設計の一貫性が保たれていない場合や経験不足のチームでは起こり得る
- リソースの表現が不適切
- 基本的に起こらないが、設計の初期段階や経験不足のチームでは起こり得る
- 自己記述的メッセージの欠如
- 基本的に起こらないが、設計の一貫性が保たれていない場合や経験不足のチームでは起こり得る
- HATEOASの欠如
- プロジェクトの特性で起こることがあり、対応に時間がかかるので省略されることがある
調べてしまうと気軽に「RESTFulです!」って言えなくなりました。
なんとなくですが、
「 1. リソース指向でない」、「 4. 統一インターフェースの欠如」、「 5. リソースの表現が不適切」、「 6. 自己記述的メッセージの欠如」になっていない前提で、
プロジェクトの制約がなく「2. HTTPメソッドの誤用」「3. ステートフル」に反していない場合に「RESTFulです!」と言ってそうな気がしました。
「 7. HATEOASの欠如」については、そもそも欠落している方が多い印象でそもそも我々のRESTFulの前提には含まない!となっている気がします。