iT邦幫忙

2025 iThome 鐵人賽

DAY 30
0
Software Development

Effect 魔法:打造堅不可摧的應用程式系列 第 30

29. Effect 生態系: `@effect/platform` 不同平台也能有一樣的 API

  • 分享至 

  • xImage
  •  

這篇要來介紹的是 @effect/platform , Effect 將不同平台的 API ,主要是 Browser 跟 Node.js 中可以共通的部份抽出來了一層 Effect 的 service ,讓你可以透過這些 service 的介面,使用同樣的 code 在不同的平台上執行,我們一起來看吧

這次的 sample code 在 https://github.com/DanSnow/ithelp-2025-ironman-sample-codes/tree/main/platform-demo

開始使用

要使用 @efect/platform 除了安裝 @effect/platform 外,通常你還需要安裝你需要執行的 platform 專屬的實作套件,我們這邊就以 Browser 跟 Node.js 兩個環境進行示範,首先,先來裝一些必要的套件

$ pnpm add effect @effect/platform @effect/platform-node @effect/platform-browser

再來安裝一些跟 demo 相關的套件

$ pnpm add --save-dev vite tsx @tsconfig/node22 typescript

使用 platform 完成主程式

這次的目標假設是我們要從 JSON Placeholder 這個提供 mocking API 的服務上面抓下 10 篇 post ,然後儲存起來吧,首先我們先用 platform 裡的 HttpClient 把 post 抓下來

import { Effect, Schema, pipe } from 'effect'
import { HttpClient, HttpClientResponse } from '@effect/platform'

// 定義一下抓下來的資料的 schema
const PostSchema = Schema.Struct({
  id: Schema.Number,
  title: Schema.String,
  body: Schema.String,
})

type Post = typeof PostSchema.Type

// 準備個 parse body 的 function
const parsePostBody = HttpClientResponse.schemaBodyJson(PostSchema)

// 實際抓取 post 的 function
const fetchPost = Effect.fn('fetchPost')(function* (id: number) {
  const post = yield* pipe(
    HttpClient.get(`https://jsonplaceholder.typicode.com/posts/${id}`),
    Effect.flatMap((response) => parsePostBody(response)),
  )
  return post
})

再來我們使用 KeyValueStore 來儲存

import { KeyValueStore } from '@effect/platform'

// 定義一下我們的 KeyValueStore 裡存的都是 Post
const { tag: PostStore, layer: PostStoreLayer } = KeyValueStore.layerSchema(PostSchema, 'PostStore')

// 儲存 post
const savePost = Effect.fn('savePost')(
  function* (id: number, post: Post) {
    const { set } = yield* PostStore
    yield* set(`post-${id}`, post)
  }, 
  // 原始的 KeyValueStore 是只能存 string 的,這個 layer 會負責做驗證,並將資料轉換成 json
  Effect.provide(PostStoreLayer)
)

最後組合起來

export function fetchAndSavePosts(count: number) {
  return pipe(
    Array.makeBy(count, (i) =>
      pipe(
        // 先抓 post
        fetchPost(i + 1),
        // 再送去儲存
        Effect.flatMap((post) => savePost(i + 1, post)),
      ),
    ),
    Effect.allWith({ concurrency: 2 }),
  )
}

到這邊我們的主程式就完成了,以上內容你可以在 core.ts 找到

在 Node.js 上執行

再來我們要讓我們的程式可以在 Node.js 上執行,其實並不難,我們只需要把 core 裡用到的 service 都提供一份平台對應的實作的 layer 就行了

// node.ts
import { Effect, Layer, pipe } from 'effect'
import { fetchAndSavePosts } from './core'
import { NodeHttpClient, NodeKeyValueStore } from '@effect/platform-node'

const NodeLive = Layer.mergeAll(
  // 指定使用 undici 來取得資料
  NodeHttpClient.layerUndici,
  // 指定 KeyValueStore 將資料存在 .cache 這個資料夾
  NodeKeyValueStore.layerFileSystem('.cache')
)

pipe(
  fetchAndSavePosts(10),
  // provide 上面那個給 Node.js 的 layer
  Effect.provide(NodeLive),
  Effect.runPromise
)

到這邊你可以試著執行看看,再去檢查一下 .cache 資料夾裡是不是有剛剛抓下來的資料

$ pnpm exec tsx node.ts

沒意外應該要有 post-1, post-2 ... 的檔案在 .cache 資料夾中

讓程式在 Browser 中執行

在 Browser 中執行跟 Node.js 要做的事情很像,就是提供一份 Browser 中使用的 layer 就可以了

import { Effect, Layer, pipe } from 'effect'
import { fetchAndSavePosts } from './core'
import { FetchHttpClient } from '@effect/platform'
import { BrowserKeyValueStore } from '@effect/platform-browser'

const BrowserLive = Layer.mergeAll(
  // 指定用 fetch 來抓資料
  FetchHttpClient.layer,
  // 指定用 local storage
  BrowserKeyValueStore.layerLocalStorage
)

pipe(
  fetchAndSavePosts(10),
  // 提供 browser 用的 layer
  Effect.provide(BrowserLive),
  Effect.runPromise
)

我還在範例 code 中準備了一個 index.html ,我們可以試著執行 vite 來觀察看看結果

$ pnpm exec vite

之後打開 http://localhost:5173 ,並打開 devtool 看一下 Network 跟 local storage,沒意外應該也會看到資料

https://ithelp.ithome.com.tw/upload/images/20251014/20111802vQngjAVgop.png

這篇我們介紹了 @effect/platform 的這個 Effect 官方提供的套件,這個套件可以幫助我們在不同的平台上使用同樣抽像化的 API ,這增加了我們程式的跨平台的能力與更容易測試,下一篇要來介紹的是另一個 Effect 的官方套件: @effect/ai

Reference


上一篇
28. 不可變的資料型態們: Array, Record, HashSet, HashMap
系列文
Effect 魔法:打造堅不可摧的應用程式30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言