在上一篇中我們講到了 DI ,這篇要來介紹 Effect 中的 DI 要如何透過 Layer 管理相依性
所以為什麼需要管理相依性,我們來講個例子,你設計了一個專門按下搖控器 (RemoteControl
) 上的電源的程式,然而搖控器需要裝電池 (Battery
) 才能運作
於是你的程式就有了以下的相依關係
轉成程式碼就是這樣
class Battery extends Effect.Service<Battery>()("Battery", {
succeed: {
powerLevel: 100
}
}) {}
class RemoteControl extends Effect.Service<RemoteControl>()("RemoteControl", {
effect: Effect.gen(function*() {
const battery = yield* Battery
return {
pressPowerButton: () =>
battery.powerLevel > 0
? Effect.void
: Effect.fail(new Error("out of bettery"))
}
})
}) {}
const program = pipe(
Effect.gen(function * () {
const control = yield * RemoteControl
yield * control.pressPowerButton()
}),
Effect.provide(RemoteControl.Default),
// 提供了 RemoteControl 後,會發現少了 Bettery ,於是要再提供必要的 Battery
Effect.provide(Battery.Default)
)
Effect.runPromise(program)
像這樣,你的 service 其實也可以依賴於其它的服務,不過如果你每次都需要寫一堆 Effect.provide
不覺的很麻煩嗎?這時候就是 Layer 出場的時機了, Layer 可以幫助你預先做好一些 service 的組合,像是 Steam 上的組合包一樣
// 結尾叫 Live 算是 Effect 中的一個開發習慣,如果是給 production 用的 layer ,結尾就會是 Live
const MainLive = pipe(
RemoteControl.Default,
Layer.provide(Battery.Default)
)
const program = pipe(
Effect.gen(function * () {
const control = yield * RemoteControl
yield * control.pressPowerButton()
}),
Effect.provide(MainLive),
)
話說你可能會想問,為什麼 service 間會有相依性要管理,一個比較常見的例子,是你建了一個用來存取資料的 service StorageAccessor
,而這個 service 底下的資料要放在哪邊,其實是由另一個 service Storage
決定的,這樣你就可以容易的抽換如何儲存,例如正式時使用檔案系統,測試時記憶體暫存
另外 Effect.Service
在建立 service 時其實可以指定預設相依的 layer 的
class RemoteControl extends Effect.Service<RemoteControl>()("RemoteControl", {
// 省略
// 像這樣就可以指定預設相依的 layer
dependencies: [Battery.Default]
}) {}
這樣 RemoteControl.Default
的這個 layer 就會預設已經有 Layer.provide(Battery.Default)
而可以直接使用了,可以到 playground 試看看
下一篇是第二篇的實戰分享,我們來寫個簡單的爬蟲看看吧