蒼時弦也
蒼時弦也
資深軟體工程師
發表於

對話階段 - Clean Architecture in TypeScript

這篇文章是 Clean Architecture in TypeScript 系列的一部分,你可以透過 Leanpub 提前閱讀內容。

前面的實作都是將對話編號寫死為 default 接下來我們要讓每個使用者開啟後都能夠產生一個新的對話,而且持續 60 分鐘都可以使用相同的對話串來進行購物。

對話編號

目前我們的設計是對話跟購物車都使用相同的 ID 辨識,這邊可以統一成叫做 Session ID 的概念,要支援這個功能我們需要有幾個地方做改動。

一個是前端需要能夠持續一段時間保存 Session ID 來使用,並且在後續所有 API 呼叫都使用這個 Session ID 來進行,後端我們還有一些假的資料,也需要清除以免有使用者遇到錯誤資料的狀況。

我們一樣使用 Claude Code 來進行修改,只需要指出相關的檔案就會自動搜尋跟調整。

 1Let's update `src/view/Chat.tsx` to provide make chatId depend on the session.
 2
 3Use Hono JSX instead of React JSX, and create a session context to manage the session ID.
 4
 5## Session Context
 6
 7Create a new `src/view/session.tsx` to provide a session context.
 8
 9- Define a `SessionContext` using `createContext`.
10- Use LocalStorage to store the session ID, make it expire after 60 minutes.
11- The session ID should be a random UUID via `crypto.randomUUID()`.
12- Add `useSession` hook to access the session context.
13
14## App Component
15
16- Update `src/view/App.tsx` to use the `SessionContext`.
17
18## Chat Component
19
20- Update `src/view/Chat.tsx` to use the `useSession` hook.
21- Replace the hardcoded `chatId` with the session ID from the context.
22
23## Backend
24
25Remove the mock data for hardcoded `chatId` in the backend.

這邊有一個小細節,因為我們提到像是 useContext 等等概念,AI 可能會預設我們要用 React 來處理,但這邊要使用 Hono JSX 作為 React 的替代,因此特別提醒後,就可以減少出錯的情況。

其他部分則詳細描述我們需要的元件以及行為,像是要定義一個叫做 SessionContext 的 Context 以及提供 useSession 的 Hook 用來取得 Session ID 等等。

等待 AI 實作完畢後,我們稍微檢查沒問題,就可以使用全新的對話進行購物。

建立對話

要測試是否有正確使用 Session ID 來切換,如果每次都等待 60 分鐘會太久,以及沒有將 ID 顯示出來都不太好觀察,我們可以加入一個 Top Bar 在畫面上方顯示 ID 以及一個按鈕用於產生新的 ID,那麼就可以快速驗證功能是否順利被實作。

1Add a top bar in chat view to display the session ID and a button to regenerate it.

基本上這個階段我們不需要給 Claude Code 太精確的說明,因為 Chat View 已經非常明顯是 src/view/Chat.tsx 的前提下,很快就可以找到對應的檔案並且進行修改。

有時候可能會把原本的功能改錯,像是將對話紀錄捲動到最底端的處理,在這一次的修改中發生問題,會把整個畫面全部捲動,我們可以給另一個提示來修正。

1The scroll to bottom will scroll all page, we expect only chat messages to scroll.

調整一小部分實作後就正常,因為 AI 的隨機性,所以並不一定會遇到這樣的問題。大多數情境只要對話紀錄還是延續的,只需要簡單說明就可以修正。

但也要評估一下當下那個問題是否很難處理,像是前端顯示的錯誤等等,如果無法精確描述外觀,大多很難修好,應該考慮直接手動修改或者提供範例。

保存對話

目前的實作都是將對話紀錄、購物車儲存在記憶體裡面,只要伺服器關閉後就會遺失資料。我們可以修改為使用 Cloudflare KV 來儲存資料,相對使用 D1 簡單也能快速提供過期機制。

 1Currently, we save the data in memory. Let's refactor to use Cloudflare KV for persistence.
 2
 3## wrangler.json
 4
 5Add new `kv_namespaces` to `wrangler.json`:
 6
 7```json
 8{
 9  "kv_namespaces": [
10	{
11	  "binding": "STORAGE",
12	  "id": "your-kv-namespace-id"
13	}
14  ]
15}
16```
17
18Run `npm cf-typegen` to update the types.
19
20## Repository
21
22Update the repository to use Cloudflare KV for data storage.
23
24We can use `import { env } from "cloudflare:workers";` to access bindings directly.
25
26For different repository, use `chat:` as prefix for keys, e.g., `chat:sessionId`.
27
28When set value, the TTL is set to 60 minutes.

為了讓實作簡單,我們使用新版的 import { env } from "cloudflare:workers"; 來處理讀取 KV Binding 的部分,後續我們再利用 Claude Code 幫我們將它改為依賴注入的版本。

修改完畢後,可能會遇到 cloudflare:workers 不存在的問題,我們最初的設定是手動將 Vite 和 Wrangler 整合起來,這邊可以改為使用 @cloudflare/vite-plugin 來處理,基本上調整完 vite.config.mts 後就能夠正常測試。

現在即使將伺服器關閉,原本的對話紀錄也會持續保存,直到該筆紀錄超過一小時都沒有變動為止(提示中要求 TTL 設定為 60 分鐘)