effect-service-design について
effect-service-design は、Effect サービスモジュールの設計や監査を支援するスキルです。プロジェクトの規約と固定された Effect のバージョンに基づき、所有境界、Layer の構成、テスト実装を検討します。
主な機能
- 01サービスと値の境界判断
- 02ポートとアダプターの配置
- 03Effect サービスモジュールの構成
- 04適切なテスト Layer の選択
- 05サービスの設計と監査
活用シーン
- 01新しい Effect サービスの設計
- 02コードベースのサービス境界の確認
- 03本番用とテスト用 Layer の構成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dmmulroy/ skills --skill effect-service-designインストール後、Claude Code で/effect-service-designを入力して読み込まれたか確認します。
同梱ファイルagents/openai.yamlreferences/AUDIT.md
詳細
Effect Service 設計
サービスは権限の境界として扱います。つまり、要件が Effect コンテキストを通じて伝播する、一貫性のある機能です。Effect サービスモジュールは、同じ機能に属するサービス契約、構築、プロダクション用 Layer、および妥当な再利用可能テスト実装を所有します。
1. ローカルルールを確認する
最も近い AGENTS.md、アーキテクチャドキュメント、コーディング規約、プロジェクトの Effect ガイダンス、固定されている Effect のバージョンとソース、および関連するベンダー提供の例を読みます。記憶している API よりも、現在のプロジェクトと互換性のあるパターンを優先します。
完了条件: 判断に後で使うため、適用されるファイルと固定されたソースの例を読み、記録していること。
2. 分岐を選ぶ
- 設計分岐: 新しいサービスまたは焦点を絞った再設計の場合は、機能の範囲を定め、呼び出し元から見える操作を 1 つ選んで、すべてのエフェクトを追跡してから、以下に進みます。
- 監査分岐: コードベース、パッケージ、機能スライス、または差分を対象とする場合は、
references/AUDIT.mdを読み、その指示に従って、以下のルールを候補すべてに適用します。
完了条件: いずれかの分岐を選び、対象範囲を定め、その範囲内で呼び出し元から見える操作を特定していること。
3. サービスの基準を適用する
実際のサービスは、少なくとも 1 つの有意義な機能を所有します。
- 永続化、認証情報、外部 I/O、ランタイムリソース、設定、時刻、乱数、またはライフサイクルに対する権限。
- エントリーポイント間で再利用される、一貫したエフェクトの順序付けまたはポリシー。
- 本番環境とテスト/ランタイム環境で実際に変わる状態または動作。
- モジュールを削除すると、複雑さが呼び出し元に広がるほどの実装上の複雑さ。
アプリケーションサービスを定義する前に、Clock、Crypto、Random、Config、HttpClient、FileSystem、Path などの既存の Effect サービスを優先します。
以下は値または純粋モジュールとして扱います。
- パース済みのドメイン入力と、呼び出しごとのリクエストデータ。
- 決定的な計算、プロジェクション、パーサー、コンストラクター。
- 1 回の呼び出しに対するポリシーを選択するオプション。
- アダプター内に限定されるフレームワークの値。
- 別のサービスの名前を変えるだけ、またはそのサービスに処理を転送するだけのラッパー。
テストだけで値を注入したいという理由では不十分です。この境界は、本番環境における実際の所有権または可変性を表している必要があります。
完了条件: 削除テストと既存サービス/アダプターの監査の両方が、「サービス」または「値」のいずれかを裏付け、採用しなかった選択肢も明確に述べていること。
4. 権限の境界を配置する
- ドメインモジュールは純粋なままにします。
- アプリケーションサービスは、操作ポリシーとアプリケーションが所有するポートを所有します。
- ポートのタグとインターフェースは、そのポートを必要とするアプリケーション操作のそばに置きます。
- 具体的なアダプターは、技術固有の
makeとlayerを所有します。アプリケーションが所有するポートと同じファイルに置く必要はありません。 - コンポジションルートは具体的な Layer を選択して提供します。再利用可能なポリシーモジュールにはしません。
- ランタイムバインディングはコンポジションルートまたはそれを所有するアダプターで yield し、その後、アプリケーション/ドメイン型の背後に隠します。
Layer の構築時に安定した依存関係を yield し、サービスメソッド内でクロージャーとして保持します。リクエスト、ファイバー、または操作のスコープに属するコンテキストは、それを使用するメソッド内で yield します。実装を実際に選択するモジュールが要件を提供するまで、要件を伝播させます。
所有するアダプターが関連するランタイム機能を yield した後も、外部ライブラリにコンストラクターオプションを渡すのは適切です。React の props、リクエスト値、ドメイン入力、フレームワークのコンストラクターは、Effect の依存性注入ではありません。
完了条件: 依存関係が内側を向き、技術固有の型がアダプターで止まり、内側の呼び出し元が自ら所有していない具体的な実装を選択していないこと。
5. Effect サービスモジュールの形を整える
プロジェクトで確立されている、これに相当する形に従います。
export interface Interface {
readonly operation: (input: Input) => Effect.Effect<Output, OperationError>
}
export class Service extends Context.Service<Service, Interface>()(
"@app/Capability",
) {}
export const make: Effect.Effect<
Service["Service"],
never,
Dependency.Service
> = Effect.gen(function* () {
const dependency = yield* Dependency.Service
const operation = Effect.fn("Capability.operation")(function* (input: Input) {
return yield* dependency.operation(input)
})
return Service.of({ operation })
})
export const layerWithoutDependencies = Layer.effect(Service, make)
export const layer = layerWithoutDependencies.pipe(
Layer.provide([Dependency.layer]),
)
export const layerTest = Layer.succeed(
Service,
Service.of({
operation: (_input) => Effect.succeed(testOutput),
}),
)
layerWithoutDependencies は、合成に必要なサービスの要件を維持します。layer は本番環境向けの完成したアセンブリで、具体的な依存関係の Layer を提供します。layerTest は、完全な静的代替実装の例です。その振る舞いを再利用でき、実態に即している場合にのみエクスポートします。インメモリ実装が観測可能な契約を忠実に維持する場合は、代わりに layerMemory を使用します。
すでに構築済みの値には Layer.succeed、遅延された同期的な構築には Layer.sync、効果を伴う取得には Layer.effect を使用します。名前の変更によって明確さが増さず、変更箇所だけが増える場合は、確立された互換性のある命名規則を維持します。
インターフェースは狭く、ドメインに即したものにします。名前付きの Effect.fn メソッド、型付きの想定エラー、yield された依存関係を使用します。オプション、メソッド、サービス、コンビネーターは、それぞれが十分な複雑さを隠す場合にのみ追加します。
完了条件: タグ、インターフェース、make、依存関係を維持する Layer、本番用の layer、エラー、メソッド、テスト方針に明確な所有者が一つずつあり、エクスポートされたすべてのシンボルが呼び出し元に必要とされていること。
6. テスト用 Layer は実態に即して選ぶ
- 完全な静的実装には
Layer.succeedを使用します。 - 再利用可能な状態、障害の注入、または観測が実際の接続点の一部である場合は、
layerTestとテスト制御サービスを追加します。 - Layer を
layerMemoryと名付けるのは、その Layer がサービスの観測可能な契約をメモリ上で忠実に実装している場合に限ります。 - 永続化、トランザクション、シリアライズ、またはプロトコルの振る舞いが重要な場合は、実際のローカル代替実装を優先します。
- テストのためだけに本番コードの公開範囲を広げることになる場合は、簡素で一度限りのフェイクをテスト内にとどめます。
テストでも、本番の呼び出し元と同じサービスインターフェースを通します。再利用可能な制御サービスがある場合は、本番用タグとテスト制御タグに同じオブジェクトを割り当てます。「使用されない」メソッドが呼び出されると異常終了する部分的なオブジェクトは、焦点を絞ったテスト用フィクスチャであり、再利用可能なインメモリアダプターではありません。
完了条件: 各テスト実装が、その名称で示す範囲を完全に実装し、テストがパブリックインターフェースを通じて結果を確認し、モックのサポートだけを目的とした新たな接続点が存在しないこと。
7. 選択した分岐を完了する
- 設計分岐: サービスにするか値にするかの判断と、その根拠を記録します。実装が求められている場合は、モジュールを新規作成またはリファクタリングし、合成ルートとテストを更新して、リポジトリで必須のチェックを実行します。
- 監査分岐: ファイル/行、またはシンボルを根拠とする優先度付きの指摘、対象となるモジュールの形、合成とテストへの影響、明示的な「維持」の判断を提示します。
完了条件: 設計した機能について、実装が求められている場合は明確な処置と検証済みの実装があること、または監査のすべての項目に処置が定められていること。検証に合格しているか、すべての失敗が報告されていること。