effect-advanced について
effect-advancedは、本番向けEffect-TSアプリの構築規約を示すスキルです。API全体の説明ではなく、型付きエラー、依存性注入、リソーススコープ、並行処理、よくある落とし穴に焦点を当てます。
主な機能
- 01Effect.genによるビジネスロジック規約
- 02型付きエラー処理パターン
- 03ServiceとLayerのアーキテクチャ
- 04スコープ付きリソース管理
- 05高水準の並行処理API
- 06落とし穴とリファレンスガイド
活用シーン
- 01Effect-TSアプリのサービス構成
- 02型付き失敗と欠陥の処理
- 03依存Layerの合成
- 04リソースのライフサイクルと並行処理の管理
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ trancong12102/ agentskills --skill effect-advancedインストール後、Claude Code で/effect-advancedを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/concurrency.mddependency-injection.mderror-handling.mdplatform.mdresource-management.mdschema.mdstream.mdtesting.md詳細
Effect Advanced:パターン、規約、落とし穴
このスキルでは、本番環境向けの Effect-TS アプリケーションを構築するためのルール、規約、アーキテクチャ上の決定事項を定めます。よくある落とし穴を防ぎ、規模の拡大にも対応できるパターンを徹底するため、意図的に特定の方針を採用しています。
API の詳細なドキュメントについては、ほかの適切なツール(ドキュメント検索、ウェブ検索など)を使用してください。このスキルでは、API 全体ではなく、Effect を慣用的に使う方法と理由に焦点を当てます。
基本規約
ビジネスロジックには Effect.gen を使用する
ジェネレーターは同期コードのように読めるため、単純な合成を超える処理では、長い .pipe / .flatMap チェーンよりも強く推奨されます。
const program = Effect.gen(function* () {
const config = yield* ConfigService;
const user = yield* UserRepo.findById(config.userId);
return user;
});
pipe はデータ変換パイプラインや短いコンビネータチェーンに使用してください。
例外をスローしない — Effect のエラーチャネルを使用する
| 代わりに… | 使用するもの |
|---|---|
throw new Error() | Effect.fail(new MyError()) |
Promise の try/catch | Effect.tryPromise({ try, catch }) |
| コールバック API | Effect.async((resume) => ...) |
| 回復不能なクラッシュ | Effect.die(defect) |
メソッドより関数を使う
合成しやすく、ツリーシェイキングにも適しているため、e.pipe(Effect.map(f)) より Effect.map(e, f) を優先してください。アプリケーションではフラットインポート(import { Effect } from "effect")を使用しても問題ありませんが、ライブラリでは名前空間インポート(import * as Effect from "effect/Effect")のほうが適しています。
@effect/schema は非推奨です
Schema はコアの effect に統合されています。"effect" から直接インポートしてください。
import { Schema } from "effect";
// NOT: import { Schema } from "@effect/schema"
本番環境では NodeRuntime.runMain を使用する
Effect.runPromise は SIGINT / SIGTERM を適切に処理しません。
import { NodeRuntime } from "@effect/platform-node";
NodeRuntime.runMain(program.pipe(Effect.provide(AppLayer)));
エラーハンドリングの考え方
Failure と defect — 根本的な違い
| 項目 | Failure(想定内) | Defect(想定外) |
|---|---|---|
| API | Effect.fail(new MyError()) | Effect.die(new Error()) |
| 型チャネル | E で追跡される | E に現れない(never) |
| 回復 | catchTag、catchAll、retry | システム境界でのみ |
| 目安 | 呼び出し元で処理する想定がある | バグ、または発生しえない状態 |
タグ付きエラーを必ず使用する
通常の Error や文字列の Failure では、Effect の型付きエラーチャネルの利点を活かせません。
class UserNotFound extends Data.TaggedError("UserNotFound")<{
readonly id: string;
}> {}
// Tagged errors are yieldable — no Effect.fail wrapper needed
const program = Effect.gen(function* () {
const user = yield* db.findUser(id);
if (!user) yield* new UserNotFound({ id });
return user;
});
catchAll は defect を捕捉しません
エラーハンドリングで最もよくある間違いです。
Effect.catchAll(program, handler); // catches E only — NOT defects
Effect.catchAllCause(program, handler); // catches everything (E + defects + interrupts)
catchAllCause / catchAllDefect は、システム境界(トップレベルのエラーハンドラー、HTTP レスポンスのマッパー)でのみ使用してください。
依存性注入のアーキテクチャ
Service → Layer → Provide(一度だけ)
1. Define services with Context.Tag → "what do I need?"
2. Implement via Layers → "how is it built?"
3. Provide once at entry point → "wire it all together"
Service メソッドの R は never にする
依存関係はメソッドのシグネチャではなく、Layer の合成で扱います。
// WRONG: leaks dependency to callers
findById: (id: string) => Effect.Effect<User, UserNotFound, Database>;
// RIGHT: Database is wired in the Layer
findById: (id: string) => Effect.Effect<User, UserNotFound>;
Layer の合成 — 演算子を理解する
| 操作 | 使用場面 | 動作 |
|---|---|---|
Layer.merge(A, B) | 独立したサービス | 両方を並行して構築する |
Layer.provide(downstream, upstream) | A が B に供給される | upstream を先に構築する |
Layer.fresh(layer) | 新しいインスタンスを強制 | メモ化を回避する |
重要: Layer.merge は構築の順序を制御しません。B が A に依存する場合は、Layer.merge ではなく Layer.provide を使用してください。
エントリーポイントで Effect.provide を一度だけ使用する
provide を分散させると、依存関係が隠れたり、Layer が重複したりします。
// WRONG: provide scattered throughout codebase
const getUser = UserRepo.findById(id).pipe(Effect.provide(DbLayer));
// RIGHT: compose and provide once
const main = program.pipe(Effect.provide(AppLayer));
NodeRuntime.runMain(main);
リソースとスコープのルール
acquireRelease には Effect.scoped が必須
Effect.scoped を忘れることは、リソース管理で最もよくある落とし穴です。プログラムが終了するまでリソースが蓄積します。
// WRONG: scope never closes, connection leaks
const result = yield * getDbConnection;
// RIGHT: scope closes when block completes
const result =
yield *
Effect.scoped(
Effect.gen(function* () {
const conn = yield* getDbConnection;
return yield* conn.query("SELECT 1");
}),
);
リリースファイナライザーは必ず実行されます
成功時、失敗時、そして中断時も、必ず実行されます。ファイナライザーには条件付きのクリーンアップに使える
Exit 値が渡されます。
1つのスコープ内で複数のリソースを扱う
Effect.scoped(
Effect.gen(function* () {
const conn = yield* Effect.acquireRelease(openConn(), closeConn);
const file = yield* Effect.acquireRelease(openFile(), closeFile);
// both released when scope closes, in REVERSE acquisition order
}),
);
並行性モデル
生のフォークより高レベル API を優先する
| API | 用途 |
|---|---|
Effect.all([], { concurrency: N }) | 並列実行数の制限 |
Effect.forEach(items, fn, { concurrency: N }) | ワーカープールパターン |
Effect.race(a, b) | 最初に完了したものが勝ち、他は中断される |
Effect.timeout(e, dur) | 任意のエフェクトに期限を設定 |
高レベル API では不十分な場合に限り、Effect.fork / Fiber を使用してください。
フォークの種類 — ライフサイクルを把握する
| 関数 | スコープ | クリーンアップ |
|---|---|---|
Effect.fork | 親のスコープ | 親とともに自動で中断される |
Effect.forkDaemon | グローバルスコープ | クリーンアップされないため、自分で行う必要がある |
Effect.forkScoped | 最も近い Scope | リソースのライフサイクルに紐付く |
注意点: forkDaemon は、中断し忘れるとファイバーがリークします。
よくある落とし穴
-
実行されないエフェクト — エフェクトを作成しても、yield するか実行しなければ、気付かれにくいバグになります。ジェネレーター内の
Effect.log("msg")は、yield*しない限り何も行いません。 -
catchAllでは欠陥を捕捉できません — すべての失敗を把握するには、システム境界でcatchAllCauseを使用してください。 -
Effect.scopedの欠落 — スコープ境界のないacquireReleaseは、プログラムが終了するまでリソースをリークします。 -
Effect.provideの散在 — すべてのレイヤーを合成し、エントリーポイントで一度だけ提供してください。 -
オーバーロードされた関数へのポイントフリー記法 —
Effect.map(myOverloadedFn)は、ジェネリクスを気付かれずに消去します。明示的なラムダを使用してください:Effect.map((x) => myOverloadedFn(x))。 -
Effect.asyncの再開関数を複数回呼び出す — 再開関数は必ず1回だけ呼び出してください。複数回呼び出すと、未定義の動作になります。 -
orDieはエラーを隠します — 型付きの失敗を型なしの欠陥に変換します。代わりに、エラーを適切に処理してください。 -
依存関係のあるサービスに
Layer.mergeを使う — merge は構築の順序を制御しません。あるレイヤーが別のレイヤーの出力を必要とする場合は、Layer.provideを使用してください。 -
Fiber.joinとFiber.awaitの違い — エッジケースでは、joinによってファイナライザーが早期に実行されることがあります。リソースの安全性が重要な場合は、awaitを優先してください。 -
無限ストリームに
runCollectを使う — 事前にtakeを呼び出さずに使用してはいけません。処理は終了せず、無制限にメモリを消費します。 -
スコープ付きテストに
it.effectを使う —Scopeを必要とするエフェクトでは、it.effectではなくit.scopedを使用してください。そうしないと、型エラーが発生します。
リファレンスファイル
特定の課題に取り組む際は、該当するリファレンスファイルをお読みください。
| ファイル | 読むタイミング |
|---|---|
references/error-handling.md | タグ付きエラー、Cause、欠陥からの復旧、エラーマッピングのパターン |
references/dependency-injection.md | サービス、Layer、合成、メモ化、provide のパターン |
references/concurrency.md | Fiber、fork のバリエーション、Deferred、Semaphore、構造化並行性 |
references/resource-management.md | Scope、acquireRelease、Layer リソース、fork と scope の相互作用 |
references/schema.md | Schema の定義、変換、ブランド型、再帰スキーマ |
references/stream.md | Stream オペレーター、チャンク化、バックプレッシャー、リソースを扱うストリーム |
references/testing.md | @effect/vitest、TestClock、Layer のモック、Config のモック |
references/platform.md | HTTP クライアント、FileSystem、Command、ランタイム、フレームワークとの統合 |