cloudflare-composition-root について
cloudflare-composition-root は、Cloudflare アプリケーションの設計とリファクタリングを支援する開発スキルです。プラットフォームのバインディングをアプリケーションのポートで隔離し、実行時の入口で依存関係やサービスを組み立てます。
主な機能
- 01実行時入口とバインディング境界の調査
- 02アプリケーション所有のポートと依存関係の設計
- 03Cloudflare バインディングのアダプター化
- 04各実行面での依存関係の組み立て
- 05実際の境界を通じた動作検証
活用シーン
- 01Cloudflare プロジェクトへのアプリケーションサービス追加
- 02バインディングの直接利用をポート経由に移行
- 03HTTP、キュー、スケジュール入口の依存関係を構成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dmmulroy/ skills --skill cloudflare-composition-rootインストール後、Claude Code で/cloudflare-composition-rootを入力して読み込まれたか確認します。
同梱ファイルEXAMPLES.md
詳細
Cloudflare コンポジションルート
コンポジションルートを使用します。各ランタイムのエントリーポイントで、生の Cloudflare 機能をアプリケーション所有の依存関係に変換し、サービスを組み立てて呼び出します。内部コードにはポートを渡し、Env、生のバインディング、フレームワークのサービスロケーター、バインディング名は渡しません。
Cloudflare entrypoint
→ tracer + parsed configuration
→ binding adapters
→ exact service dependencies
→ application service
→ protocol projection
TypeScript のコントラクト、解析、エラー、副作用、テストには ../coding-standards/SKILL.md を適用してください。実装の形が明確でない場合は、Hono、WorkerEntrypoint、アダプター、リファクタリングのテンプレートについて、EXAMPLES.md を参照してください。
所有権
- エントリーポイントアダプター: 外部リクエスト/イベントを解析し、呼び出しコンテキストを確立し、依存関係を構築して、サービスを呼び出し、その結果を変換します。
- バインディングアダプター: KV、R2、D1、Durable Objects、Queues、サービスバインディング、Workflows、またはその他の外部機能を使用して、アプリケーション所有のポートを実装します。
- アプリケーションサービス: 1 つのユースケースにおける順序付け、フォールバック、再試行、ベストエフォートの方針を担います。
- ドメインモジュール: 純粋な値と不変条件を担います。
- ポート: 利用側が必要とする機能のみを正確に表し、Cloudflare API をそのまま模倣しません。
生のプラットフォーム値を置けるのは、コンポジションルートとバインディングアダプターだけです。バインディングアダプターには、Env 全体ではなく、必要最小限のプラットフォーム機能を渡します。
1. 境界を把握する
リポジトリの指示と、影響を受ける次の項目を確認します。
- Hono アプリファクトリー、ミドルウェアの順序、ルート登録、コンテキスト型。
- Worker、WorkerEntrypoint、Durable Object、Workflow、キュー、スケジュール済みのエントリーポイント。
Env宣言と、関連する各バインディングのすべての使用箇所。- トレーサーの作成、相関付け、エラー報告、切り離された作業の所有権。
- 既存のサービス、アダプター、ポート、テスト、アーキテクチャチェック。
影響を受ける動作を、エントリーポイントから副作用、呼び出し元から見える結果までたどります。リファクタリングでは、移動する前に現在のエラー、再試行、フォールバック、シリアライズ、トレースの動作を記録します。
完了基準: 影響を受けるすべてのエントリーポイント、バインディング利用箇所、動作、ライフサイクル、副作用、失敗時の方針、既存のテスト用接点が把握されています。
2. アプリケーション所有の依存関係を設計する
利用側のニーズに基づいてコントラクトを定義します。
/** Persistence capability required by document application services. */
export interface DocumentStore {
/** Find one document by its domain ID. */
find(id: DocumentId): Promise<Document | null>;
/** Persist one valid document. */
save(document: Document): Promise<void>;
}
/** Exact dependencies of the publish-document use case. */
export type PublishDocumentDependencies = Readonly<{
documents: DocumentStore;
clock: Clock;
tracer: Tracer;
}>;
ルール:
- 生のキー、文字列、リクエストオブジェクト、バインディングオプションではなく、ドメイン入力と解析済みの出力を使用します。
- 実際の呼び出し元が必要とする操作だけを公開します。
- 各サービスの依存関係オブジェクトは厳密に保ちます。共有の巨大なバッグを作ったり、
EnvをDependenciesに名前変更したりしてはいけません。 - プラットフォームのシリアライズ、キー空間、ページネーショントークン、TTL、メタデータ、出力の解析は、バインディングアダプターに配置します。
- ポリシーはアプリケーションサービスに担わせます。サービスが回復可能な状態と、破損や欠陥を区別できるよう、アダプターで障害を正確に変換します。
- スパンを作成するサービスとアダプターには
Tracerを注入します。すべてのメソッドシグネチャに追加してはいけません。 - スコープがコンポジション後に実際に動的となる場合に限り、アプリケーション所有のファクトリーを追加します。たとえば、認証後にアカウントに紐づくインスタンスを構築する場合です。ファクトリーから
Envや生のバインディングを公開してはいけません。
ライフタイムを明示的に選択します。
- バインディングとトレーサーが並行する呼び出し間で安全な場合は、不変のアダプターとサービスを再利用します。
- リクエストスコープのランタイムオブジェクトや変更可能な呼び出し状態を保持する場合は、呼び出しごとに構築します。
- 切り離された作業は、限定されたバックグラウンドタスク用ポートなど、明示的な所有者に渡します。
ExecutionContextを内部に漏らしてはいけません。
完了基準: 内部のすべてのシグネチャがアプリケーション/ドメイン型を使用し、各操作とファクトリーに現在の呼び出し元があり、依存関係、トレーサー、副作用のライフタイムに明示的な所有者がいます。
3. 構築またはリファクタリングする
新しいサービスブランチ
- Hono や Cloudflare のランタイム型をインポートせずに、ポートに対してアプリケーションサービスを実装します。
- 外部の役割ごとにバインディングアダプターを 1 つ実装します。
WorkersKvDocumentStoreのように、テクノロジー固有の実装には明示的な名前を付けます。 - プロトコルに関する処理は、HTTP、RPC、キュー、Workflow、またはスケジュール済みアダプターに置きます。
- ユースケースを提供するすべてのエントリーポイントでサービスを組み立てます。
既存コードブランチ
- 根拠が不足している箇所では、現在の公開境界にテストを追加し、呼び出し元から見える動作を維持します。
- 直接の利用側で現在使われている操作をもとに、その利用側にポートを定義します。
- ポリシーを変更する前に、既存の生のバインディングをアダプターの背後に置きます。
- 直接の利用側が持つ生の依存関係をポートに置き換えます。
- アダプターの構築を 1 層ずつ外側に移します。中間層が受け渡すのは型付きポートとし、ポートと生のバインディングの両方を恒久的な設計として渡してはなりません。
- 複数の関心事が混在するモジュールを分割します。ポリシーはアプリケーションサービスへ、プラットフォーム固有の処理はアダプターへ、プロトコルのマッピングはエントリーポイントに残します。
- 最後の呼び出し元を移行した後、不要になった
Envパラメーター、ファクトリー、汎用ラッパー、重複するパーサーやキー生成処理、互換性のためのオーバーロードを削除します。
対称性だけを理由に、無関係なバインディングまでリファクタリングを広げないでください。
完了条件: 新しいコードが、サービスを提供するすべてのサーフェスで完全に組み立てられているか、移行済みの生のバインディングがすべてアダプターにのみ到達します。動作やポリシーの変更は、存在しないか、明示的に要求され、テストされています。
4. すべてのランタイムサーフェスで組み立てる
各コンポジションルートで、次の順に処理します。
- 設定とエントリーポイントのプロパティを解析します。
- トレーサーを作成するか、取得します。
- トレース対象の処理を呼び出す前に、相関関係を確立します。
- 必要に応じてトレーサーを注入し、生のバインディングをアダプターでラップします。
- サービスに必要な依存関係だけを組み立てます。
- 薄いプロトコルアダプターを介してサービスを呼び出します。
- 結果と安全なエラーを外部の契約にマッピングします。
Hono の場合:
createApp(dependencies)と、厳密な依存関係をクロージャーで保持するルート/ミドルウェアファクトリーを優先します。- ルートのトレーシングミドルウェアで使うトレーサーを注入し、アプリの深い層で無関係なトレーサーを作成しないでください。
- 依存関係が呼び出し単位の場合、エントリーポイントから生のバインディングをクロージャーで保持する、範囲の狭いリクエスト組み立て関数を渡します。この関数は、相関関係の確立後、依存関係を最初に所有するミドルウェアから呼び出します。
- Hono の変数には、型付けされた厳密な機能だけを格納します。コンポジションミドルウェアもルートモジュールも、
context.envを介して生のバインディングにアクセスしてはなりません。
WorkerEntrypoint、Durable Objects、Workflows、キュー、スケジュール済みハンドラーでは、それぞれのランタイムコンストラクターまたはハンドラーを独立したコンポジションルートとして扱います。あるサーフェスの依存関係グラフやライフサイクルが、別のサーフェスにも適合するとは限りません。
完了条件: サービスを提供するすべてのサーフェスでトレーシングを確立し、依存関係グラフ全体を構築しています。内部モジュールがランタイムリソースを取得したり、バインディング名を読み取ったりすることはありません。
5. 境界を検証する
本番環境の境界を通じて、次を検証します。
- 実際のポートを実装する小規模な記録用フェイクを使った、アプリケーションサービスの動作。
- Cloudflare の代表的なローカルランタイムに対する、バインディングアダプターのシリアライズ、解析、失敗時の動作。
- エントリーポイントでの組み立てと、外部向けの結果/エラーへの投影。
- スパン名、相関関係、安全な属性、切り離された作業の所有権。
- 型チェック、対象を絞ったテスト、アーキテクチャチェック、リポジトリで必須の検証。
たとえば、プロジェクトのバインディング名やランタイム型を使って、漏出箇所を検索します。
rg 'Env|KVNamespace|R2Bucket|D1Database|DurableObjectNamespace|ExecutionContext' src
見つかった箇所をすべて、コンポジションルート、バインディングアダプター、避けられないフレームワーク宣言、または違反に分類します。また、ルート、サービス、ドメイン、機能モジュールでのcontext.envへの直接アクセスや、バインディング名の参照も検索します。
完了条件: プラットフォームの生の参照がすべて分類され、許可されています。変更したポート、アダプター、サービス、エントリーポイント、失敗経路、ライフサイクルにはすべて根拠があります。また、Cloudflare のランタイム型をインポートまたは受け取るアプリケーションモジュールはありません。