Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Effect-TS をなぜ採用し、なぜ継続できたか? / Why Effect-TS? And...
Search
asa1984
July 28, 2026
1k
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Effect-TS をなぜ採用し、なぜ継続できたか? / Why Effect-TS? And why do we keep using it?
イベント:
Effect-TS「継続」と「撤退」の境界線〜TSKaigi登壇者たちのパネルトーク〜
asa1984
July 28, 2026
More Decks by asa1984
See All by asa1984
Real World Effect-TS: 堅牢なプロダクトを型で組み上げる
asa1984
2
820
Real World Nix CI/CD編
asa1984
3
750
Nix入門パラダイム編
asa1984
6
1.8k
Featured
See All Featured
Six Lessons from altMBA
skipperchong
29
4.5k
Tips & Tricks on How to Get Your First Job In Tech
honzajavorek
1
730
DBのスキルで生き残る技術 - AI時代におけるテーブル設計の勘所
soudai
PRO
68
57k
AI Search: Where Are We & What Can We Do About It?
aleyda
0
7.9k
AI in Enterprises - Java and Open Source to the Rescue
ivargrimstad
0
1.5k
Neural Spatial Audio Processing for Sound Field Analysis and Control
skoyamalab
0
480
SEOcharity - Dark patterns in SEO and UX: How to avoid them and build a more ethical web
sarafernandez
0
270
How to make the Groovebox
asonas
2
2.4k
Fantastic passwords and where to find them - at NoRuKo
philnash
52
3.8k
A designer walks into a library…
pauljervisheath
211
25k
From Legacy to Launchpad: Building Startup-Ready Communities
dugsong
0
330
HDC tutorial
michielstock
2
860
Transcript
Effect-TS をなぜ採用し、 なぜ継続できたか? 株式会社HERP, asa1984 Effect-TS「継続」と「撤退」の境界線 2026-07-28
自己紹介 asa1984 読み: アサヒ 2025年、株式会社HERP に新卒入社 『ジョブミル』の開発を経て、現在は『HERP AI Recruiter』を担当 やっていること
フルスタックで機能開発・運用 最近はコードベースの治安維持活動に従事 Effect-TS をなぜ採用し、なぜ継続できたか? 実家の猫の尻尾 01
前回のあらすじ Effect-TS をなぜ採用し、なぜ継続できたか? 02
目次 1. レイヤードアーキテクチャと Effect-TS 2. 標準ライブラリとしての Effect-TS Effect-TS をなぜ採用し、なぜ継続できたか? 03
最初に: Effect-TS とは? 一言で言うと Promise を Effect に置き換える Effect<A, E,
R>; // │ │ └─ Requirements: // │ └──────── Error: // └───────────────── 依存、環境、副作用 起こりうるエラー 成功時の値 Effect-TS をなぜ採用し、なぜ継続できたか? 04
レイヤードアーキテクチャと Effect-TS Effect-TS をなぜ採用し、なぜ継続できたか? 05
ジョブミル 人材紹介会社向けのマルチテナント SaaS 複数の採用管理システムに散らばっ た求人を取り込み、一元管理する Effect-TS をなぜ採用し、なぜ継続できたか? 06
システム構成(簡略図) クライアント React SPA 外部システム 採用管理システム GraphQL バックエンド GraphQL サーバー
データストア PostgreSQL 求人取り込み Effect-TS をなぜ採用し、なぜ継続できたか? OpenSearch バッチジョブ 07
レイヤー別の Effect-TS の import 率 各レイヤーで effect / @effect/* を
import しているファイルの割合 ユースケース PostgreSQL OpenSearch サーバー ドメイン Effect-TS をなぜ採用し、なぜ継続できたか? 96.3% 90.3% 72.7% 52.3% 31.4% 08
ドメイン よく使う API domain Effect Schema Effect Option Schema Other
Context.Tag Context Effect-TS をなぜ採用し、なぜ継続できたか? 基本的には通常の TypeScript で型や ロジックが定義されている。 09
ドメイン: サービスのインターフェースを定義する 求人リポジトリのインターフェース // class JobRepository extends Context.Tag("JobRepository")< JobRepository, {
find: (id: Job.ID) => Effect.Effect<Job.Entity | null>; store: (job: Job.Entity) => Effect.Effect<Job.Entity>; /* */ } >() {} 中略 Context.Tag を用いて依存のインターフェースを定義する Effect-TS をなぜ採用し、なぜ継続できたか? 10
ユースケース よく使う API use-cases Effect Effect Other Effect.gen() Layer Match
Effect-TS をなぜ採用し、なぜ継続できたか? リポジトリ全体で見ても `Effect.gen()` が最頻出の API になっている 11
Promise と async / await const fetchUser: () => Promise<User>;
const fetchPosts: (userId: UserID) => Promise<Post[]>; const main = async () => { const user = await fetchUser(); const posts = await fetchPosts(user.id); return { user, posts }; }; Effect-TS をなぜ採用し、なぜ継続できたか? 12
Effect.gen() const fetchUser: () => Effect.Effect<User>; const fetchPosts: (userId: UserID)
=> Effect.Effect<Post[]>; const main = Effect.gen(function* () { const user = yield* fetchUser(); const posts = yield* fetchPosts(user.id); return { user, posts }; }); Effect-TS をなぜ採用し、なぜ継続できたか? 13
Promise と Effect の対応 Promise then のチェーン async / await
Effect と Effect.flatMap Effect.gen と yield* pipe Effect-TS をなぜ採用し、なぜ継続できたか? 14
合成 const program = Effect.gen(function* () { const suggestion =
yield* findSuggestion({ id }); // SuggestionNotFound yield* removeSuggestion({ suggestion }); // SuggestionAlreadyRecommended return suggestion.id; }); の型。 すると勝手に と // program yield* Error Requirements type Program = Effect.Effect< Suggestion.ID, SuggestionNotFound | SuggestionAlreadyRecommended, Suggestion.Repository | Recommendation.Repository >; Effect-TS をなぜ採用し、なぜ継続できたか? が合成される。 15
規約: 原則 Effect.gen() を用いてコードを記述する Effect-TS をなぜ採用し、なぜ継続できたか? 16
永続化と検索サービス prisma Effect search Effect Effect Stream よく使う API Effect.promise()
Layer.effect() Schema Schema Other Layer Effect-TS をなぜ採用し、なぜ継続できたか? 17
Promise を Effect に変換する の // JobRepository find const find
= (id: Job.ID) => Effect.promise(() => prisma.job.findUnique({ where: { id, organizationID } }), ); は Promise が reject したとき defect を発生させる defect: 回復不能な異常 Effect.promise Effect-TS をなぜ採用し、なぜ継続できたか? 18
Two Types of Error API 型 Effect.promise Effect<A, never> Effect.tryPromise
Effect<A, UnknownException> Repository では基本的に Effect.promise を使っている インフラのエラーはアプリケーションのロジックで回復しない判断をしている Effect-TS をなぜ採用し、なぜ継続できたか? 19
アプリケーション GraphQL server Effect Effect Option Schema Layer Match Other
Effect-TS をなぜ採用し、なぜ継続できたか? よく使う API Effect.catchTags() Effect.provide() Effect.run* Layer.* 20
エラーハンドリング 成功 findSuggestion removeSuggestion SuggestionNotFound SuggestionAlready Recommended 失敗 GraphQL レスポンス
レスポンス Effect.catchTags GraphQL エラー Effect.die プログラムの出口で合成されたエラーをパターンマッチし、適切な出力に変換する Effect-TS をなぜ採用し、なぜ継続できたか? 21
依存の構築と注入 依存の実体を にする // Layer const OpenSearchClientLive = Layer.succeed(OpenSearchClient, opensearchClient);
// → Layer<OpenSearchClient, never, never> に注入する // Effect program.pipe(Effect.provide(OpenSearchClientLive)); // Effect<A, E, OpenSearchClient> → Effect<A, E, never> は Tag とその実装を受け取ってサービスを構成する になるまでプログラムは実行できない Layer.succeed R never が Effect-TS をなぜ採用し、なぜ継続できたか? 22
型で見える依存関係のグラフ // Layer<JobRepository, never, PrismaClient | Organization> const JobRepositoryLive =
Layer.effect( JobRepository, Effect.gen(function* () { const prisma = yield* PrismaClient; const organization = yield* Organization; // /* */ }), ); 中略 テナント情報 組み立てに他のサービスが必要な場合は Layer.effect 必要な依存は Layer の 3 つ目の型引数に現れる Effect-TS をなぜ採用し、なぜ継続できたか? 23
Effect.run* の // GraphQL resolver const exit = await program.pipe(
Effect.provide(context.layers.global), // Effect.provide(Layer.succeed(Organization, context.organization)), Effect.runPromiseExit, ); 認証で確定した組織をここで注入する は純粋な値なので Effect.run* に渡すまで実行されない この例では runPromiseExit を使い、defect を安全にキャッチできるようにし ている Effect Effect-TS をなぜ採用し、なぜ継続できたか? 24
標準ライブラリとしての Effect-TS Effect-TS をなぜ採用し、なぜ継続できたか? 25
スキーマ のレスポンスのスキーマ // API const Candidate = Schema.Struct({ birthday: Schema.optional(
Schema.String.annotations({ jsonSchema: { format: "date", type: "string" }, }), ), /* */ }).annotations({ identifier: "Candidate" }); 中略 Zod や Valibot と同じ Standard Schema v1 に対応したスキーマを内包している Effect-TS をなぜ採用し、なぜ継続できたか? 26
可観測性 Observability 系の API がコア API として提供されている: ロガー トレース メトリクス
Effect にはトレース用のメタデータを保存する領域が備わっている Effect-TS をなぜ採用し、なぜ継続できたか? 27
可観測性: 構造化ログの属性を付与する program.pipe( /* */ Effect.annotateLogs({ "graphql.field.name": info.fieldName, "organization.id": context.organization.id,
"user.id": context.user.id, }), Effect.runPromiseExit, ); 中略 これ以降、program の内側で出るすべてのログの属性に ID が付く Effect-TS をなぜ採用し、なぜ継続できたか? 28
可観測性: span を作る Effect.gen(function* () { /* */ 中略 yield*
Effect.annotateCurrentSpan({ "llm.model": model, "llm.input_tokens": usage.input_tokens, "llm.output_tokens": usage.output_tokens, }); }).pipe(Effect.withSpan("LLMClient.createMessages()")); Effect-TS をなぜ採用し、なぜ継続できたか? 29
可観測性: 出力先も注入する Layer.mergeAll( Logger.makeLayer(logger), // Tracer.layerGlobal, // OpenTelemetry /* */
); 中略 ログの出力先 の Tracer は Effect に注釈を与えるだけ 依存と同様、出力する実体は後から注入する annotateLogs , withSpan ※ ジョブミルでは Effect-TS 組み込みのロガー( Effect.log )ではなく winston をラップした自前のカスタムロガーを定義して差し 替えている。 Effect-TS をなぜ採用し、なぜ継続できたか? 30
並行処理: 構造化並行性 Effect.gen(function* () { const counts = yield* Effect.all(
{ advertisedJobs: count.advertisedJobs(query), jobs: count.jobs(query), manualJobs: count.manualJobs(query), sharedJobs: count.sharedJobs(query), }, { concurrency: "unbounded" }, ); }); どれかが失敗したら残りは自動で中断される Effect-TS をなぜ採用し、なぜ継続できたか? 31
並行処理: 並列度の制限 へ並列でリクエストを送る処理 // LLM Effect.forEach(jobs, extractSearchFields, { concurrency: llmConcurrency,
}); 一度に大量のリクエストを送ってレート制限を超過しないように並列度を制限する Effect-TS をなぜ採用し、なぜ継続できたか? 32
並行処理: リトライ へリクエストを送る処理 // LLM invoke.pipe( Effect.retry({ // schedule: Schedule.jittered(Schedule.exponential(1_000,
2)), times: 9, // while: (e) => e instanceof RateLimited || e instanceof ServiceUnavailable, }), ); 指数バックオフ 一時的なエラーの場合はリトライする Effect-TS をなぜ採用し、なぜ継続できたか? 33
その他 寿命つきリソースの管理 や S3 におけるストリーミング処理 サーバー起動時の環境変数のパース の構築 Scope : Stream
: OpenSearch Config : Command : CLI Effect-TS をなぜ採用し、なぜ継続できたか? 34
まとめ Effect-TS を用いてレイヤードアーキテクチャを実践している 上手く活用すれば Effect-TS だけで多くのことをまかなえる 侵襲性の高さの裏返し Effect.gen() が楽 Effect-TS
をなぜ採用し、なぜ継続できたか? 35
ありがとうございました Effect-TS をなぜ採用し、なぜ継続できたか? 36
付録: 集計方法と注意点 対象は git ls-files '*.ts' '*.tsx' の 5,890 ファイル。
node_modules や生成物は含まない。 各ファイル内の import 文を解析し、そのファイルで Effect-TS 由来と確定した名 前空間についてのみ Namespace.member を数える。 @effect/vitest の it / expect / describe はテスト用のため集計対 象から除外している。 Effect-TS をなぜ採用し、なぜ継続できたか? 37