iT邦幫忙

2023 iThome 鐵人賽

DAY 27
0
Modern Web

從 Next.js 開始的 Functional Programming系列 第 27

D27 - 管理依賴注入 (一)

  • 分享至 

  • xImage
  •  

假設我們的課程管理系統需要提供教學資源上傳下載的服務,經過一番研究後,我們選擇使用 MinIO 來儲存 binary data 、使用 MongoDB 來儲存 meta data。而且因為我們的課表系統會部署在不同地方,因此 MongoDB 和 MinIO 都需要讀取環境變數 Env。

最終各種服務就形成了如同下圖的依賴關係。

https://ithelp.ithome.com.tw/upload/images/20231012/201586150N0x17AkDh.png

假設我們有一個 Env 服務長這個樣子 。

//src\service\env.ts

interface EnvService {
  load: () => Effect.Effect<never, EnvError, Env>
}

const EnvService = {
  context: Context.Tag<EnvService>(),
}

利用 EffectLayer 這個工具,就可以把它的其中一種實作方式包裝起來,例如以下這樣

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 的組合與運用方式。


上一篇
D26 - 更多依賴注入
下一篇
D28 - 管理依賴注入 (二)
系列文
從 Next.js 開始的 Functional Programming30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言