Claude-skill-registry api-extend
API拡張ワークフロー(ports → provider → handler → Mock → テスト → フロント型 → UI)を自動化
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/api-extend" ~/.claude/skills/majiayu000-claude-skill-registry-api-extend && rm -rf "$T"
manifest:
skills/data/api-extend/SKILL.mdsource content
API拡張ワークフロー
概要
新しいAPIフィールドやエンドポイントを追加する際の定型フローを自動化します。DDD(ドメイン駆動設計)とクリーンアーキテクチャの原則に従い、以下の8ステップを実行します:
- ports定義 - ドメイン層のインターフェース定義
- provider実装 - JRA-VAN APIプロバイダー実装
- Mock実装 - モックプロバイダー実装
- handler実装 - APIハンドラー(Lambda関数)
- テスト作成/更新 - TDDに基づくテストコード
- フロント型定義 - TypeScript型とマッピング関数
- APIクライアント - フロントエンドAPIクライアント更新
- UI実装 - React コンポーネント更新
入力形式
スキル呼び出し時に以下の情報を提供してください:
/api-extend 追加内容: - データクラス名: <DataClass名>(例: TrainingData) - フィールド: - <field_name>: <type> - <説明> - <field_name>: <type> - <説明> - エンドポイント(新規の場合): GET /path/{param} - 既存エンドポイントへの追加(の場合): <エンドポイント名>
実行プロセス
ステップ1: ports定義(ドメイン層)
ファイル:
main/backend/src/domain/ports/race_data_provider.py
パターン:
@dataclass(frozen=True) class <DataClass>: """<説明>.""" field_name: type field_name: type | None = None # オプショナルフィールド class RaceDataProvider(ABC): @abstractmethod def get_<method_name>(self, <params>) -> <ReturnType>: """<説明>.""" pass
重要な規則:
- すべてのデータクラスは
でイミュータブル@dataclass(frozen=True) - abstractmethodには必ず
を記述pass - 型ヒントは必須(
形式)str | None
ステップ2: JRA-VAN Provider実装
ファイル:
main/backend/src/infrastructure/providers/jravan_race_data_provider.py
パターン:
def get_<method_name>(self, <params>) -> <ReturnType>: """<説明>.""" try: response = self._session.get( f"{self._base_url}/<endpoint>", params={"param": value} if needs_params else {}, timeout=self._timeout, ) if response.status_code == 404: return None # または [] response.raise_for_status() # リストの場合 data = response.json() return [self._to_<data_class>(d) for d in data] # 単一オブジェクトの場合 return self._to_<data_class>(response.json()) except requests.RequestException as e: logger.warning(f"Could not get <resource>: {e}") return None # または [] def _to_<data_class>(self, data: dict) -> <DataClass>: """API レスポンスを <DataClass> に変換する.""" return <DataClass>( field_name=data["field_name"], optional_field=data.get("optional_field"), )
重要な規則:
- 404エラーは
または空リストNone
を返す[] - その他のエラーは警告ログを出力してデフォルト値を返す
- 必ず
変換メソッドを実装_to_<data_class>
でオプショナルフィールドを取得data.get("key")
ステップ3: Mock Provider実装
ファイル:
main/backend/src/infrastructure/providers/mock_race_data_provider.py
パターン:
def get_<method_name>(self, <params>) -> <ReturnType>: """<説明>.""" import random random.seed(_stable_hash(str(<unique_key>)) % (2**32)) # データを生成 results = [] for i in range(<count>): data = <DataClass>( field_name=random.choice(self.SAMPLE_DATA), numeric_field=random.randint(min, max), optional_field=random.choice([...]) if needed else None, ) results.append(data) return results
重要な規則:
- 必ず
でシード固定(再現可能なデータ生成)_stable_hash() - クラス定数として
を定義SAMPLE_DATA - ランダム生成でもリアルなデータ分布を意識
ステップ4: API Handler実装
ファイル:
main/backend/src/api/handlers/races.py
レスポンス構築パターン:
def get_<endpoint>(event: dict, context: Any) -> dict: """<説明>. GET /<path>?param=value Query/Path Parameters: param: 説明 Returns: レスポンス説明 """ # パラメータ取得 param = get_query_parameter(event, "param") # または param = get_path_parameter(event, "param") if not param: return bad_request_response("param is required") # ユースケース実行 provider = Dependencies.get_race_data_provider() result = provider.get_<method>(param) if result is None: return not_found_response("<Resource>") # レスポンス構築 response_data = [ { "field_name": item.field_name, "optional_field": item.optional_field, } for item in result ] return success_response({"data": response_data})
重要な規則:
- snake_case → camelCase 変換は不要(バックエンドはsnake_case統一)
- 必ず入力バリデーション
- エラーレスポンスは
,bad_request_response()
を使用not_found_response()
ステップ5: テスト作成/更新
ファイル:
main/backend/tests/ 配下の適切な場所
テストパターン:
def test_get_<method_name>(): """<説明>.""" # Arrange provider = MockRaceDataProvider() # Act result = provider.get_<method_name>(<params>) # Assert assert result is not None assert len(result) > 0 assert result[0].field_name == expected_value
重要な規則:
- AAA パターン(Arrange, Act, Assert)
- Mockプロバイダーを使用してテスト
- 境界値・エラーケースもテスト
ステップ6: フロントエンド型定義
ファイル:
main/frontend/src/types/index.ts
パターン:
// API形式(バックエンドレスポンス) export interface Api<DataClass> { field_name: string; optional_field?: number; } // フロントエンド表示用 export interface <DataClass> { fieldName: string; optionalField?: number; } // 変換関数 export function mapApi<DataClass>To<DataClass>(api: Api<DataClass>): <DataClass> { return { fieldName: api.field_name, optionalField: api.optional_field, }; }
重要な規則:
- API型は
プレフィックス(snake_case)Api<Name> - フロントエンド型は camelCase
- 必ずマッピング関数を提供
ステップ7: APIクライアント更新
ファイル:
main/frontend/src/api/client.ts
パターン:
async get<Resource>(<params>): Promise<ApiResponse<<DataClass>>> { const response = await this.request<Api<DataClass>Response>( `/endpoint/${encodeURIComponent(param)}` ); if (!response.success || !response.data) { return { success: false, error: response.error }; } return { success: true, data: mapApi<DataClass>To<DataClass>(response.data), }; }
重要な規則:
- URLパラメータは
でエンコードencodeURIComponent() - レスポンス型チェックを必ず実施
- エラーハンドリングを統一
ステップ8: UI実装ガイダンス
対象ファイル:
main/frontend/src/pages/*Page.tsx, main/frontend/src/components/*
提供内容:
- どのコンポーネントを更新すべきか
- 新しいフィールドの表示例(Tailwind CSS)
- 状態管理パターン(useState, useEffect)
出力形式
以下の形式で進捗を報告:
✅ ステップ1: ports定義完了 - <DataClass>を追加 - get_<method>メソッドを追加 ✅ ステップ2: JRA-VAN Provider実装完了 - get_<method>を実装 - _to_<data_class>変換メソッドを実装 ✅ ステップ3: Mock Provider実装完了 - サンプルデータ生成ロジックを実装 ✅ ステップ4: API Handler実装完了 - GET /<endpoint> エンドポイントを実装 ✅ ステップ5: テスト作成完了 - test_get_<method> を追加 ✅ ステップ6: フロントエンド型定義完了 - Api<DataClass>, <DataClass> 型を追加 - マッピング関数を追加 ✅ ステップ7: APIクライアント更新完了 - get<Resource>メソッドを追加 ⏳ ステップ8: UI実装ガイダンス - 更新対象: <component_path> - 表示例コードを提供 次のアクション: - [ ] テスト実行(pytest) - [ ] フロントエンドビルド確認(npm run build) - [ ] デプロイ前チェック(./scripts/pre-deploy-check.sh)
エラーハンドリング
頻出エラーと対処法
-
ImportError: frozen dataclassのimport漏れ
- 対処:
を確認from dataclasses import dataclass
- 対処:
-
型エラー: Optional型の扱い
- 対処:
形式を使用(str | None
は使わない)Optional[str]
- 対処:
-
Mock更新漏れ
- 対処: 必ず Mock Provider も同時に更新
-
フロントエンド型不整合
- 対処:
で確認npm run typecheck
- 対処:
使用例
例1: 馬の血統情報を追加
/api-extend 追加内容: - データクラス名: PedigreeData - フィールド: - horse_id: str - 馬ID - horse_name: str | None - 馬名 - sire_name: str | None - 父馬名 - dam_name: str | None - 母馬名 - broodmare_sire: str | None - 母父馬名 - エンドポイント: GET /horses/{horse_id}/pedigree
例2: 既存APIに馬体重フィールドを追加
/api-extend 追加内容: - データクラス名: WeightData - フィールド: - weight: int - 馬体重(kg) - weight_diff: int - 前走比増減 - 既存エンドポイントへの追加: GET /races/{race_id} のrunnersに追加
参照コード
実装時は以下のファイルを参照:
- ports定義:
main/backend/src/domain/ports/race_data_provider.py - JRA-VAN Provider:
main/backend/src/infrastructure/providers/jravan_race_data_provider.py - Mock Provider:
main/backend/src/infrastructure/providers/mock_race_data_provider.py - API Handler:
main/backend/src/api/handlers/races.py - フロント型:
main/frontend/src/types/index.ts - APIクライアント:
main/frontend/src/api/client.ts
注意事項
- TDD原則: テストを先に書いてから実装(Red → Green → Refactor)
- イミュータブル: すべてのデータクラスは
frozen=True - DDD原則: ドメインロジックはポート・アダプターパターンを遵守
- 型安全性: フロントエンドは必ず
で確認npm run typecheck - git worktree: 作業は必ず feature ブランチで(main直接push禁止)