假設我們的課程管理系統需要提供教學資源上傳下載的服務,經過一番研究後,我們選擇使用 MinIO 來儲存 binary data 、使用 MongoDB 來儲存 meta data。而且因為我們的課表系統會部署在不同地方,因此 MongoDB 和 MinIO 都需要讀取環境變數 Env。
最終各種服務就形成了如同下圖的依賴關係。
假設我們有一個 Env 服務長這個樣子 。
//src\service\env.ts
interface EnvService {
load: () => Effect.Effect<never, EnvError, Env>
}
const EnvService = {
context: Context.Tag<EnvService>(),
}
利用 Effect
的 Layer
這個工具,就可以把它的其中一種實作方式包裝起來,例如以下這樣
const EnvLayer = Layer.succeed(
EnvService.context,
EnvService.context.of({ load: ()=>Env.of(process.env) })
)
使用 Layer.succeed 表示這個服務沒有依賴任何其他外部服務
有些服務
執行
的時候會出錯,例如我們的Env.load
,但在建立
服務的時候並不會執行它,所以一定不會出錯,請注意執行時機。
有了最上層 Env 的 Layer,我們再依序建立 MongoService 和 MinIoService 的 Layer
MongoDB
nterface MongoService {
createCourseMeta: (
course: Course
) => Effect.Effect<never, MongoServiceError, Course>
onst MongoService = {
context: Context.Tag<MongoService>(),
onst MongoLayer = Layer.effect(
MongoService.context, // 目標要產生的 Service
Effect.map(EnvService.context, (env) => //從依賴的 Service 產生目標 Service 的過程
MongoService.context.of({
createCourseMeta: (course: Course) => {
// use env to do something
return Effect.succeed(course)
},
})
)
``
MinIO
nterface MinIoService {
createCourseObject: (
data: string
) => Effect.Effect<never, MinIoServiceError, void>
onst MinIoService = {
context: Context.Tag<MinIoService>(),
onst MinIoServiceLayer = Layer.effect(
MinIoService.context,
pipe( // Effect.map(a,b) 換成 pipe(a,Effect.map(b)) 意思一樣
EnvService.context,
Effect.map((env) =>
MinIoService.context.of({
createCourseObject: (data: string) => {
// use env to do something
return Effect.succeed((() => {})())
},
})
)
)
``
最後我們用 Effect.all 來把兩個 service 合併起來,並且用從中取得的前面兩個 service 建立我們最終的目標,FileService
interface FileService {
createCourse: (
data: string
) => (course: Course) => Effect.Effect<never, FileServiceError, Course>
}
const FileService = {
context: Context.Tag<FileService>(),
}
const FileServiceLayer = Layer.effect(
FileService.context,
pipe(
Effect.all([MongoService.context, MinIoService.context]),
Effect.map(([MinIoService, MongoService]) =>
FileService.context.of({
createCourse: (data: string) => (course: Course) => {
// use MinIoService, MongoService to do something
return Effect.succeed(course)
},
})
)
)
)
可以想像
MongoService.context
的型別Context.Tag<MongoService, MongoService>
就是Effect<MongoService, never, MongoService>
all 的用途是把 array of effect 變成 effect of array
所以[Effect<..., MongoService>, Effect<..., MinioService>]
就變成了Effect<...,[MongoService, MinioService]>
我們可以把 Service 當作一個型別,就像是 number 或是 string 一樣,而 Layer 的工作就是建立 Service 專用的 pipeline,來把複雜的依賴關係理清楚。今天主要講到的是 Layer 的建立方式,明天再繼續介紹 Layer 的組合與運用方式。