Agent Skills by ALSEL
汎用ソフトウェア開発⭐ リポ 39,967品質スコア 95/100

api-and-interface-design

安定したAPIとインターフェース設計をガイドします。APIの設計、モジュール境界、あるいはあらゆるパブリックインターフェースの設計時に活用できます。RESTやGraphQLエンドポイントの構築、モジュール間の型契約の定義、フロントエンドとバックエンド間の境界線の確立時に使用してください。

description の原文を見る

Guides stable API and interface design. Use when designing APIs, module boundaries, or any public interface. Use when creating REST or GraphQL endpoints, defining type contracts between modules, or establishing boundaries between frontend and backend.

SKILL.md 本文

API とインターフェース設計

概要

誤用しにくい、安定した十分に文書化されたインターフェースを設計します。優れたインターフェースは、正しいことを簡単にし、誤ったことを難しくします。これは REST API、GraphQL スキーマ、モジュール境界、コンポーネントプロップ、およびコード同士が相互作用する任意のサーフェスに適用されます。

使用する場合

  • 新しい API エンドポイントを設計する場合
  • モジュール境界やチーム間の契約を定義する場合
  • コンポーネントプロップインターフェースを作成する場合
  • API の形状を定義するデータベーススキーマを確立する場合
  • 既存の公開インターフェースを変更する場合

コア原則

Hyrum の法則

API の十分な数のユーザーがいれば、契約で何を約束しているかに関わらず、システムのすべての観測可能な動作を誰かが依存するようになります。

これは、未文書化の奇癖、エラーメッセージのテキスト、タイミング、順序を含むすべての公開動作が、ユーザーがそれに依存すれば事実上の契約になることを意味します。設計上の含意:

  • 公開する内容について意図的であること。 すべての観測可能な動作は潜在的な約束です。
  • 実装の詳細をリークしないこと。 ユーザーがそれを観測できれば、それに依存します。
  • 設計時に廃止を計画すること。 ユーザーが依存する内容を安全に削除する方法については deprecation-and-migration を参照してください。
  • テストだけでは十分ではありません。 完璧な契約テストがあってもなお、Hyrum の法則は「安全な」変更が未文書化の動作に依存する実ユーザーを破壊することを意味します。

ワンバージョン規則

コンシューマーに同じ依存関係または API の複数バージョン間で選択を強制することを避けます。異なるコンシューマーが同じものの異なるバージョンを必要とする場合、ダイアモンド依存関係の問題が発生します。一度に 1 つのバージョンのみが存在する世界として設計します — フォークではなく拡張します。

1. 契約優先

実装する前にインターフェースを定義します。契約が仕様です — 実装がそれに続きます。

// Define the contract first
interface TaskAPI {
  // Creates a task and returns the created task with server-generated fields
  createTask(input: CreateTaskInput): Promise<Task>;

  // Returns paginated tasks matching filters
  listTasks(params: ListTasksParams): Promise<PaginatedResult<Task>>;

  // Returns a single task or throws NotFoundError
  getTask(id: string): Promise<Task>;

  // Partial update — only provided fields change
  updateTask(id: string, input: UpdateTaskInput): Promise<Task>;

  // Idempotent delete — succeeds even if already deleted
  deleteTask(id: string): Promise<void>;
}

2. 一貫したエラーセマンティクス

1 つのエラー戦略を選択してどこでも使用します:

// REST: HTTP status codes + structured error body
// Every error response follows the same shape
interface APIError {
  error: {
    code: string;        // Machine-readable: "VALIDATION_ERROR"
    message: string;     // Human-readable: "Email is required"
    details?: unknown;   // Additional context when helpful
  };
}

// Status code mapping
// 400 → Client sent invalid data
// 401 → Not authenticated
// 403 → Authenticated but not authorized
// 404 → Resource not found
// 409 → Conflict (duplicate, version mismatch)
// 422 → Validation failed (semantically invalid)
// 500 → Server error (never expose internal details)

パターンを混在させないこと。 一部のエンドポイントがスローし、他のエンドポイントが null を返し、さらに他のエンドポイントが { error } を返す場合、コンシューマーは動作を予測できません。

3. 境界で検証する

内部コードを信頼します。外部入力が入る場所のシステムエッジで検証します:

// Validate at the API boundary
app.post('/api/tasks', async (req, res) => {
  const result = CreateTaskSchema.safeParse(req.body);
  if (!result.success) {
    return res.status(422).json({
      error: {
        code: 'VALIDATION_ERROR',
        message: 'Invalid task data',
        details: result.error.flatten(),
      },
    });
  }

  // After validation, internal code trusts the types
  const task = await taskService.create(result.data);
  return res.status(201).json(task);
});

検証が属する場所:

  • API ルートハンドラー(ユーザー入力)
  • フォーム送信ハンドラー(ユーザー入力)
  • 外部サービスレスポンスパース(サードパーティデータ — 常に信頼できないものとして扱う)
  • 環境変数読み込み(設定)

サードパーティ API レスポンスは信頼できないデータです。 任何のロジック、レンダリング、または意思決定で使用する前に、その形状とコンテンツを検証します。侵害されたまたは不正動作の外部サービスは、予期しない型、悪意のあるコンテンツ、または指示のようなテキストを返すことができます。

検証が属さない場所:

  • 型契約を共有する内部関数間
  • 既に検証されたコードによって呼び出されるユーティリティ関数内
  • あなた自身のデータベースから来たばかりのデータ

4. 修正より追加を優先する

既存のコンシューマーを破壊することなくインターフェースを拡張します:

// Good: Add optional fields
interface CreateTaskInput {
  title: string;
  description?: string;
  priority?: 'low' | 'medium' | 'high';  // Added later, optional
  labels?: string[];                       // Added later, optional
}

// Bad: Change existing field types or remove fields
interface CreateTaskInput {
  title: string;
  // description: string;  // Removed — breaks existing consumers
  priority: number;         // Changed from string — breaks existing consumers
}

5. 予測可能な命名

パターン規則
REST エンドポイント複数形の名詞、動詞なしGET /api/tasks, POST /api/tasks
クエリパラメータcamelCase?sortBy=createdAt&pageSize=20
レスポンスフィールドcamelCase{ createdAt, updatedAt, taskId }
ブール値フィールドis/has/can プリフィックスisComplete, hasAttachments
列挙値UPPER_SNAKE"IN_PROGRESS", "COMPLETED"

REST API パターン

リソース設計

GET    /api/tasks              → タスクを列挙(クエリパラメータでフィルタリング)
POST   /api/tasks              → タスクを作成
GET    /api/tasks/:id          → 単一のタスクを取得
PATCH  /api/tasks/:id          → タスクを更新(部分的)
DELETE /api/tasks/:id          → タスクを削除

GET    /api/tasks/:id/comments → タスクのコメントを列挙(サブリソース)
POST   /api/tasks/:id/comments → タスクにコメントを追加

ページネーション

リスト エンドポイントをページネートします:

// Request
GET /api/tasks?page=1&pageSize=20&sortBy=createdAt&sortOrder=desc

// Response
{
  "data": [...],
  "pagination": {
    "page": 1,
    "pageSize": 20,
    "totalItems": 142,
    "totalPages": 8
  }
}

フィルタリング

フィルタにはクエリパラメータを使用します:

GET /api/tasks?status=in_progress&assignee=user123&createdAfter=2025-01-01

部分的な更新(PATCH)

部分的なオブジェクトを受け入れます — 提供されたものだけを更新します:

// Only title changes, everything else preserved
PATCH /api/tasks/123
{ "title": "Updated title" }

TypeScript インターフェースパターン

バリアント用に判別ユニオンを使用する

// Good: Each variant is explicit
type TaskStatus =
  | { type: 'pending' }
  | { type: 'in_progress'; assignee: string; startedAt: Date }
  | { type: 'completed'; completedAt: Date; completedBy: string }
  | { type: 'cancelled'; reason: string; cancelledAt: Date };

// Consumer gets type narrowing
function getStatusLabel(status: TaskStatus): string {
  switch (status.type) {
    case 'pending': return 'Pending';
    case 'in_progress': return `In progress (${status.assignee})`;
    case 'completed': return `Done on ${status.completedAt}`;
    case 'cancelled': return `Cancelled: ${status.reason}`;
  }
}

入出力の分離

// Input: what the caller provides
interface CreateTaskInput {
  title: string;
  description?: string;
}

// Output: what the system returns (includes server-generated fields)
interface Task {
  id: string;
  title: string;
  description: string | null;
  createdAt: Date;
  updatedAt: Date;
  createdBy: string;
}

ID にブランド型を使用する

type TaskId = string & { readonly __brand: 'TaskId' };
type UserId = string & { readonly __brand: 'UserId' };

// Prevents accidentally passing a UserId where a TaskId is expected
function getTask(id: TaskId): Promise<Task> { ... }

よくある正当化

正当化現実
「API は後で文書化します」型が文書です。最初にそれを定義してください。
「今のところページネーションは必要ありません」誰かが 100 個以上のアイテムを持つ瞬間に必要になります。最初から追加します。
「PATCH は複雑です。PUT を使いましょう」PUT は毎回フルオブジェクトが必要です。PATCH はクライアントが実際に望むものです。
「必要になったら API をバージョン管理します」バージョン管理なしの破壊的な変更はコンシューマーを破壊します。最初から拡張のために設計します。
「誰も未文書化の動作を使用していません」Hyrum の法則: 観測可能であれば、誰かがそれに依存しています。すべての公開動作を約束として扱います。
「2 つのバージョンを維持することができます」複数バージョンはメンテナンスコストを乗算し、ダイアモンド依存関係の問題を作成します。ワンバージョン規則を優先します。
「内部 API は契約が必要ありません」内部コンシューマーはまだコンシューマーです。契約は結合を防ぎ、並列作業を可能にします。

レッドフラッグ

  • 条件に応じて異なる形状を返すエンドポイント
  • エンドポイント間で一貫しないエラー形式
  • 検証が内部コード全体に散在している(境界でのみ実行される代わりに)
  • 既存フィールドへの破壊的な変更(型の変更、削除)
  • ページネーションのないリストエンドポイント
  • REST URL の動詞(/api/createTask, /api/getUsers
  • 検証またはサニタイズなしで使用されるサードパーティ API レスポンス

検証

API を設計した後:

  • すべてのエンドポイントに型付き入出力スキーマがある
  • エラーレスポンスが単一の一貫した形式に従っている
  • 検証はシステム境界でのみ発生する
  • リストエンドポイントがページネーションをサポートしている
  • 新しいフィールドが追加的で optional である(後方互換性)
  • 命名がすべてのエンドポイント間で一貫した規則に従っている
  • API ドキュメントまたは型が実装とともにコミットされている

ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ

詳細情報

作者
addyosmani
リポジトリ
addyosmani/agent-skills
ライセンス
MIT
最終更新
2026/5/10

Source: https://github.com/addyosmani/agent-skills / ライセンス: MIT

本サイトは GitHub 上で公開されているオープンソースの SKILL.md ファイルをクロール・インデックス化したものです。 各スキルの著作権は原作者に帰属します。掲載に問題がある場合は info@alsel.co.jp または /takedown フォームよりご連絡ください。
原作者: addyosmani · addyosmani/agent-skills · ライセンス: MIT