【API】"RESTの原則"をアンチパターンを基に噛み砕いてみました
APIの設計とか開発とか、結構やること多いけど、ちゃんと抑えてねぇなぁと思い今回まとめてみました。
各原則の理解がふわっとでも理解できればと思い、アンチパターンを基に説明します。
結構長いですので、忙しい方はここだけでも 👋
では、さっそく6つの原則を紹介していきます。
と、その前にRESTってなに?
何度調べてもなんだかよく分からない概念ランキングの個人的 Top5 に入る言葉。
それが「REST」だ。
よくわかんないで有名なんですね。
Representational State Transfer (REST) は、大まかに言うと API の動作に条件を課すソフトウェアアーキテクチャです。
定義が抽象的すぎて、ピンとこないですよね。
私も調べてみました。
ただ、どのサイトも似たような表現がいまいち理解しにくいな、ってところが正直な感想です。
(人によって解釈が違いそう。なのでいろんなRESTがありそうですね。)
いろいろ調べた中で私はこう理解しています。
RESTfulの原則とは、RESTを満たすための原則(ルール)のようです。
調べてる中で感じたんですが解釈には個人差があるみたいで、RESTとは、"APIの設計思想"、"システムの設計思想"、"API設計手法"など、かなり理解の仕方が曖昧なようです。
私は、以下の記事参考にさせていただきました。
すべての Web サービス設計者に捧ぐ「RESTful って結局なんなんだ」
本記事では、「RESTについて」の深堀りはこの辺で...。
REST原則 について - 概要
REST原則自体も定義が曖昧みたいです。(4つだったり6つだったり意見が分かれることもあるみたいです)
今回は、以下6つご紹介します。
主語を意識すると理解しやすいかもです。
- Client-Server: クライアントとサーバーは役割を明確に分ける
- Stateless: APIは状態を保持せず、リクエスト単位で完結させる
- Cacheable: APIはキャッシュ可能にする(Statelessのため処理が重くなるから仕方ないですよね)
- Uniform Interface: (HTTPの場合)リソースへのやり取り(CRUD)はメソッド(GET, POST, PUT, DELETE)を適切に利用する
- Layered System: システムの設計はレイヤー構造に分け、拡張性を高める
- Code on Demand(Optional): クライアントにコードを送信・実行させる
さてここからは、アンチパターンを例に各原則を噛み砕こうと思います。
REST原則について - 詳細
原則①. Client-Server
クライアントとサーバーの責務をそれぞれ明確にしましょう。
- Client: APIを利用しリソースのリクエストを行う
- Server: リクエストに応じてリソースを提供する
🆖 アンチパターン(サーバーがHTMLを返す / クライアントがDBに直接アクセス)
echo "<html><body><h1>" . $user_name . "</h1></body></html>";
const result = database.query("SELECT * FROM users WHERE id = 1");
console.log(result);
🆗 デザインパターン(APIを通じてJSONを返す)
fetch('/api/users/1')
.then(response => response.json())
.then(data => console.log(data));
header('Content-Type: application/json');
echo json_encode(['id' => 1, 'name' => 'John Doe']);
原則②. Stateless
サーバーは状態を保持せず、すべてのリクエストは独立して処理されるべきです。
ここでいう "状態" はログイン状態、カートの状態、などだと理解しやすいかと。
🆖 アンチパターン(セッションを利用)
session_start();
$_SESSION['cart'][] = ['item' => 'Laptop', 'price' => 1000];
echo json_encode(['message' => 'Item added to cart']);
(ロードバランシング等で)サーバーが複数台ある場合、リクエストごとにカート状態がリセットされそうですね。突然カート状態が復活することとかも起きそう。ステートフルですね。
🆗 デザインパターン(クライアント側で状態を管理)
let cart = JSON.parse(localStorage.getItem('cart')) || [];
cart.push({ item: 'Laptop', price: 1000 });
localStorage.setItem('cart', JSON.stringify(cart));
fetch('/api/checkout', {
method: 'POST',
body: JSON.stringify({ cart }),
headers: { 'Content-Type': 'application/json' }
});
$cart = json_decode(file_get_contents('php://input'), true)['cart'];
echo json_encode(['message' => 'Checkout successful', 'total' => array_sum(array_column($cart, 'price'))]);
これだと、ステートレスなのでどのサーバーにリクエストが送られても問題ないですね。
※ ローカルストレージだとセキュリティ面のケアが気になるところですが、本題ではないのでスルーしてください。
原則③. Cacheable
APIのレスポンスはキャッシュ可能であるべきです。
(Cache-Controlヘッダーや、プロキシ、CDNの利用)
HTTP/1.1 200 OK
+ Cache-Control: public, max-age=3600
Content-Type: application/json
{
"id": 1,
"name": "Product A",
"price": 1000
}
原則④. Uniform Interface
- APIの設計は統一されたルールに基づくべきであり、一貫性を持たせましょう
- 適切なHTTPメソッド(GET, POST, PUT, DELETE)を利用しましょう
- リソース指向(Resource-Oriented)で設計し、URIは「名詞」を使いましょう
🆖 アンチパターン(リソース名に動詞を含む/メソッドが曖昧、POSTなのかGETなのかわからない)
POST /api/getUser
Content-Type: application/json
{
"id": 1
}
🆗 デザインパターン(RESTfulな設計)
GET /api/users/1
POST /api/users
PUT /api/users/1
DELETE /api/users/1
直感的ですよね。
原則⑤. Layered System
- APIの設計はレイヤー(層)を意識し、それぞれの役割を分離する
- 各レイヤーの役割が明確(凝集性に富んでる)
- クライアントはシステムの内部構造を知らなくてもよい
※ ここは特にですが、"APIの設計思想"ではなく"システムの設計思想"の原則と理解したほうが飲み込みやすいかもです。
🆖 アンチパターン(レイヤー分離がない)
// ビジネスロジックとデータベースアクセスが混在
$user = $_POST['user'];
$db = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$stmt = $db->prepare("INSERT INTO users (name) VALUES (:name)");
$stmt->bindParam(':name', $user);
$stmt->execute();
header('Content-Type: application/json');
echo json_encode(['message' => 'User created']);
🆗 デザインパターン(レイヤー構造の具体例)
- プレゼンテーション
- ユースケース
- リポジトリ
- インフラ
- etc...
原則⑥. Code on Demand
※ この項目は任意です。
- クライアントは必要に応じて、サーバーからスクリプトやコードを受け取り、実行できるべき
- クライアントの動作を動的に変更し、柔軟性を高められる
🆖 アンチパターン(クライアントがすべてのロジックを保持)
function validateInput(value) {
return value.length > 5; // 長さチェックのみ
}
document.getElementById("input").addEventListener("input", (event) => {
console.log(validateInput(event.target.value));
});
🆗 デザインパターン(サーバーからバリデーションルールを提供)
header('Content-Type: application/javascript');
echo 'function validateInput(value) { return value.length > 8; }';
fetch('/api/validation-rules.js')
.then(response => response.text())
.then(script => eval(script));
document.getElementById("input").addEventListener("input", (event) => {
console.log(validateInput(event.target.value));
});
// プロジェクトによってはクライアント+サーバーどちらもバリデーションかけるパターンも多いと思いますが一例です
まとめ
今回は、RESTの原則について私なりの理解をまとめてみました。
やっぱり例があるとわかりやすいですね。
参考
余談
知らなかったんですが、"アンチパターン" の対義語は "デザインパターン" なんですね。
Discussion