
架構規劃 - Clean Architecture in TypeScript
今年(2025 年) ThroughWorks 的 Technology Radar 中提及了 AI-friendly code design 的概念,討論以往軟體開發的優良實踐,也能夠對 AI 協力開發產生幫助,因此列入的評估的範圍中,在為來可能會成為採用的選項之一。
即使我們仍不確定未來會如何變化,但是在良好的軟體架構跟實踐為基礎,仍可以幫助我們更好地應用 AI 來進行開發。
Clean Architecture
對於使用大型語言模型(LLM,Large Language Model)提供脈絡(Context)可以改善表現已經是常識一樣的概念,那麼在軟體中採用容易理解的架構也能有不錯的效果,而 Clean Architecture 就是其中一種可行的方式。
過去幾年思考和嘗試 Clean Architecture 的應用後,得出了一個很容易在初期上手的技巧——將商業邏輯抽象化。
然而,抽象化會導致「認知負擔(Cognitive load)」但是適當的抽象化處理,可以很好地提高軟體的可維護性,因此抽象化的核心就在於我們該怎麼界定抽象化處理。
商業邏輯
在 Clean Architecture 中使用了四個環的範例來切分,然而本質上是將商業邏輯跟非商業邏輯的部分一分爲二,透過定義介面(Interface)進行抽象化,接著實作 Interface Adapter 來讓非商業邏輯的部分能被商業邏輯使用。
舉例來說,我們想要將一個叫做 CartItem
的物件保存起來,如果沒有使用上述的技巧,實作可能是這樣的。
1class CartController {
2 // ...
3 async post(params): Promise<Response> {
4 // ...
5 const res = await this.db.execute(
6 "INSERT INTO cart_items (cart_id, product_id, name, price) VALUES (?, ?, ?, ?)",
7 cartItem.cartId,
8 cartItem.productId,
9 cartItem.name,
10 cartItem.price
11 )
12 // ...
13 }
14}
我們會直接取用跟商業邏輯無關的元件(資料庫)來處理,這樣會造成一些問題,像是跟使用 SQL 的資料庫機制耦合、暴露過多細節等等。
雖然 AI 可以很好地理解這些,但同時也在擴大 Context(脈絡)的需求,那麼就有可能發生注意力遺失(Loss Attention)這類現象,這也會影響到我們在程式碼生成的成效。
若要改善這個問題,我們只需要將商業邏輯(購物車品項管理)跟非商業邏輯(資料保存)加入抽象化處理,在 TypeScript 中只需要定義一個介面就可以達到必要的效果。
1interface CartItemRepository {
2 save(item: CartItem): Promise<void>
3}
4
5class CartController {
6 // ...
7 async post(params): Promise<Response> {
8 // ...
9 await this.repository.save(cartItem)
10 // ...
11 }
12}
那麼,不論是使用 RDBMS(關聯式資料庫)還是 NoSQL 的方式保存資料,都跟當下的商業邏輯無關,我們只關注「購物車品項需要持久保存」這一件事情,因為這是我們的商業需求。
架構規劃
基於上述的切分方式,我們可以再更近一步對非商業邏輯的部分進行細分,這些分類大多是遵照單一職責原則來劃分的,至少我們不會希望處理 API 請求的行為跟資料庫的處理混在一起。
我們可以沿用 Clean Architecture in Go 的規劃經驗,這一次的專案大致上我們會有以下幾種物件類型。
商業邏輯類
src/
├─ usecase/
├─ entity/
非商業邏輯
src/
├─ controller/
├─ repository/
├─ presenter/
├─ service/
├─ agent/
前端(非商業邏輯)
src/
├─ api/
├─ view/
後續的章節會進一步說明每一個元件的應用,基本上我們只需要了解到當我們擁有這樣的架構規劃後,就能讓 AI 代理(Agent)透過類似這樣的提示(Prompt)來實作。
1根據規格文件 `docs/get_cart_items.md` 中的描述,實作一個 API 端點 `/api/cart_items` 你需要修改以下檔案
2
3* 更新 `CartItemRepository` 的 Postgres 實作,加入讀取資料的行為
4* 加入 `ListCartItem` 的 UseCase
5* 加入 `JsonCartItemPresenter` 的實作
6* 更新 `CartItemController` 支援讀取 CartItem 並且回傳 JSON
7* 更新 `src/config/routes.ts` 加入新的端點
8
9團隊的慣例,請參考 `docs/convention.md`
上述有不少描述可以利用 IDE(如:VSCode、Cursor)的提示樣板功能標準化,但大多數情境就足夠讓 AI 完成一定程度的實作,透過限定每個檔案的功能,可以有效的降低錯誤機率和遵守實作的標準。
這些都會是讓 AI 協作的品質可控,也能夠在較大規模修改下還能保持可審查(Code Review)的基礎。
如果對這篇文章有興趣,可以透過以下連結繼續閱讀這系列的其他文章。