Effect.Service
是把「tag + 預設實作 + 對應的 Layer」合在一起的語法糖。
很適合應用程式層級的服務(有合理預設實作)。
目標情境
步驟 1:定義服務契約(先定 API 形狀與錯誤型別)
class Cache extends Context.Tag("Cache")<Cache, {
readonly lookup: (key: string) => Effect.Effect<string, PlatformError>
}>() {}
為何需要手動標註 PlatformError?
- 因為在 Context.Tag 的「契約宣告階段」你就必須決定錯誤型別。由於 lookup 的實作會呼叫 FileSystem API,其錯誤即為 PlatformError,所以契約中選擇 PlatformError 最吻合實際行為。
步驟 2:提供實作(在層內取得依賴並實作 lookup)
// 建立快取服務的實作
const cacheLive = Effect.gen(function*() {
const fs = yield* FileSystem.FileSystem
const path = yield* Path.Path
const cacheDir = path.join("src", "day24", "cache")
const lookup = (key: string) => fs.readFileString(path.join(cacheDir, key))
return { lookup }
})
步驟 3:組裝依賴(顯式依賴組裝)
// 將實作包成 Layer,並提供 Node 檔案系統與 Path 依賴
const CacheLayer = Layer.effect(Cache, cacheLive).pipe(
Layer.provide(NodeFileSystem.layer),
Layer.provide(Path.layer)
)
步驟 4:使用與執行
// 主程式:取得快取服務 → 讀取資料 → 確保結尾換行 → 輸出到 stdout
const program = Effect.gen(function*() {
const cache = yield* Cache
const data = yield* cache.lookup("my-key")
const line = data.endsWith("\n") ? data : `${data}\n`
process.stdout.write(line)
}).pipe(Effect.catchAllCause((cause) => Console.log(cause)))
const runnable = program.pipe(Effect.provide(CacheLayer))
// 執行程式
Effect.runFork(runnable)
適用場合
步驟 1:定義服務 + 實作 + 依賴(在同一處)
// 以 Effect.Service 定義快取服務,並在 effect 區塊中提供實作
class Cache extends Effect.Service<Cache>()("Cache", {
effect: Effect.gen(function*() {
// 取得檔案系統與路徑服務
const fs = yield* FileSystem.FileSystem
const path = yield* Path.Path
// 快取目錄位置(專案內的 src/day24/cache)
const cacheDir = path.join("src", "day24", "cache")
// 確保目錄存在(必要時遞迴建立)
const lookup = (key: string) => fs.readFileString(path.join(cacheDir, key))
return { lookup }
}),
// 宣告此服務啟動時所需的外部依賴 Layer
dependencies: [NodeFileSystem.layer, Path.layer]
}) {}
步驟 2:使用與執行(更少樣板)
const program = Effect.gen(function*() {
const cache = yield* Cache
const data = yield* cache.lookup("my-key")
const line = data.endsWith("\n") ? data : `${data}\n`
process.stdout.write(line)
}).pipe(Effect.catchAllCause((cause) => Console.log(cause)))
const runnable = program.pipe(Effect.provide(Cache.Default))
Effect.runFork(runnable)
這裡為何不需要手動標註 PlatformError 呢?
- 因為 lookup 的錯誤型別會從 FileSystem API 的型別自動推斷出來(就是 PlatformError),不需你在服務宣告時手動寫出。
適用場合
以下兩種策略都適用,請依需要選擇:
const FileSystemTest = FileSystem.layerNoop({
readFileString: () => Effect.succeed("File Content...")
})
const TestLayer = Cache.DefaultWithoutDependencies.pipe(
Layer.provide(FileSystemTest),
Layer.provide(Path.layer)
)
const runnable = program.pipe(
Effect.provide(TestLayer)
)
Effect.runFork(runnable)
// 建立假的 Cache
const cache = new Cache({
lookup: () => Effect.succeed("Cache Content...")
})
const runnable = program.pipe(Effect.provideService(Cache, cache))
Effect.runFork(runnable)
Effect.Service
Context.Tag + Layer
Effect.provideService(MyService, mock)
。