idempotency について
バックエンドの冪等性を設計するためのコーディングスキルです。キーを使い、重要な変更操作を再試行しても副作用が重複しない方法を示します。
主な機能
- 01冪等性キーのパターン
- 02キー生成と保存の指針
- 03重複リスクの検出
- 04再試行時のレスポンス再利用
活用シーン
- 01決済エンドポイントの二重請求防止
- 02注文やアカウントの重複作成防止
- 03フロントエンドの重複防止に依存する API の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ yanko-belov/ code-craft --skill idempotencyインストール後、Claude Code で/idempotencyを入力して読み込まれたか確認します。
詳細
冪等性
概要
重要な操作は安全に再試行できる必要があります。冪等性キーを使用してください。
ネットワーク障害は発生します。クライアントは再試行します。ユーザーはボタンを二度クリックします。冪等性がなければ、再試行によって二重請求や重複注文、データ破損が発生します。
使用する場面
- 決済処理のエンドポイント
- 注文の作成
- 二度実行されるべきではないあらゆる操作
- 重複防止を「フロントエンドに任せる」よう求められた場合
絶対的なルール
NEVER rely on frontend to prevent duplicate requests.
例外はありません:
- 「フロントエンドでボタンを無効にする」場合でも
- 「読み込み中の状態を表示する」場合でも
- 「めったに起きない」場合でも
- 「ユーザーは二度クリックしない」場合でも
検出:重複リスクの兆候
ミューテーションに重複防止がない場合は、作業を止めてください:
// ❌ VIOLATION: No idempotency protection
app.post('/payments', async (req, res) => {
const { userId, amount, cardToken } = req.body;
// If this request retries, user gets charged twice!
const payment = await stripeCharge(amount, cardToken);
await db.payments.create({ userId, amount, stripeId: payment.id });
res.json({ success: true });
});
起こりうる問題:
- ネットワークのタイムアウト → クライアントが再試行 → 二重請求
- ユーザーが二度クリック → 2つのリクエスト → 二重請求
- モバイルアプリの再試行ロジック → 複数のリクエスト
正しいパターン:冪等性キー
// ✅ CORRECT: Idempotency key protection
app.post('/payments', async (req, res) => {
// Require idempotency key
const idempotencyKey = req.headers['idempotency-key'];
if (!idempotencyKey) {
return res.status(400).json({
error: 'Idempotency-Key header is required'
});
}
const { userId, amount, cardToken } = validated(req.body);
// Check for existing request with this key
const existing = await db.idempotencyKeys.findOne({
where: { key: idempotencyKey, userId }
});
if (existing) {
// Return cached response
return res.status(existing.statusCode).json(existing.response);
}
try {
// Process the payment
const payment = await stripeCharge(amount, cardToken);
await db.payments.create({ userId, amount, stripeId: payment.id });
const response = { success: true, paymentId: payment.id };
// Cache the response
await db.idempotencyKeys.create({
key: idempotencyKey,
userId,
statusCode: 200,
response,
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000) // 24 hours
});
res.json(response);
} catch (error) {
// Cache error responses too (optional, depends on error type)
throw error;
}
});
// Client usage:
// POST /payments
// Headers: { "Idempotency-Key": "user-123-order-456-attempt-1" }
冪等性キーの設計
キーの生成(クライアント側)
// Option 1: UUID per request
const key = crypto.randomUUID();
// Option 2: Deterministic (better for retries)
const key = `${userId}-${orderId}-${timestamp}`;
// Option 3: Hash of request content
const key = hash(JSON.stringify({ userId, items, amount }));
キーの保存(サーバー側)
interface IdempotencyRecord {
key: string;
userId: string;
statusCode: number;
response: any;
createdAt: Date;
expiresAt: Date; // Clean up old keys
}
冪等性が必要な操作
| 操作 | リスク | 解決策 |
|---|---|---|
| 決済 | 二重請求 | 冪等性キー |
| 注文の作成 | 重複注文 | 冪等性キー |
| 在庫の減算 | 過剰な減算 | 冪等性キー |
| メールの送信 | 重複メール | 冪等性キー |
| アカウントの作成 | 重複アカウント | 一意制約+冪等性 |
圧力に屈しないためのプロトコル
1. 「フロントエンドで重複を防げます」
圧力:「ボタンを無効にして、読み込み中の状態を表示します」
回答: ネットワークは自動的に再試行します。JavaScript はクラッシュします。ユーザーは素早く操作します。
対応: バックエンドで冪等性を確保してください。フロントエンドの UX は保護策ではありません。
2. 「めったに起きません」
圧力:「重複はまれな例外ケースです」
回答: まれな事象でも、ユーザー数が多ければ多くのユーザーを怒らせます。二重請求が一度起きるだけで、サポートは大変なことになります。
対応: 重要なミューテーションはすべて保護してください。
3. 「ユーザーは二度クリックしません」
圧力:「ユーザーは慎重に操作します」
回答: ユーザーの接続が遅いこともあります。ボタンが小さいこともあります。いら立ちからクリックしてしまうこともあります。
対応: ユーザーの行動を決して当てにしないでください。
4. 「データベースに一意制約があります」
圧力:「重複した挿入は失敗します」
回答: 一意制約はエラーを発生させます。ユーザーにはエラーが表示されます。UX は最悪です。
対応: 冪等性によって同じ成功レスポンスを返してください。
危険信号 — 作業を止めて再検討してください
- 冪等性のない決済エンドポイント
- 「重複防止はフロントエンドで対応する」
- ネットワークの再試行によって副作用が発生する
- 二重請求の報告がユーザーから寄せられている
- Idempotency-Key ヘッダーに対応していない
これらに該当する場合は、冪等性による保護を追加してください。
クイックリファレンス
| 安全でない方法 | 安全な方法 |
|---|---|
| フロントエンドを信頼する | 冪等性キーを必須にする |
| 重複時にエラーを返す | キャッシュしたレスポンスを返す |
| リクエストは1回だけだと想定する | 再試行を前提に設計する |
| POST は常に新しいリソースを作成する | POST +キー=最大1回の実行 |
よくある正当化(すべて誤り)
| 言い訳 | 実際のところ |
|---|---|
| 「フロントエンドで防げる」 | ネットワークは再試行します。ユーザーは二度クリックします。 |
| 「めったに起きない」 | まれな事象でも、規模が大きければ多くのインシデントにつながります。 |
| 「ユーザーは慎重に操作する」 | ユーザーは人間です。 |
| 「一意制約がある」 | 制約が返すのは成功ではなくエラーです。 |
| 「複雑すぎる」 | サポートチケットへの対応より簡単です。 |
要点
重要なミューテーションでは、冪等性キーを必須にしてください。
フロントエンドの保護を決して当てにしないでください。冪等性キーごとにレスポンスをキャッシュしてください。重複したリクエストには同じレスポンスを返してください。古いキーは定期的に削除してください。