Skip to content

Head First Software Architecture

htlin222 edited this page Jul 16, 2026 · 1 revision

用《Head First Software Architecture》看 openevidence-mcp

《Head First Software Architecture》(Raju Gandhi、Mark Richards、Neal Ford,O'Reilly 2024)是一本把「軟體架構」講得很白話的入門書。這頁用它的框架當作放大鏡,一邊解釋書裡的概念,一邊對照 openevidence-mcp 實際怎麼做——目標是:就算你沒讀過這本書,讀完這頁也能看懂本專案「為什麼長這樣」。

書的骨架很好記:四個維度 + 兩條定律。我們照這個順序走。


先分清楚:架構 vs 設計

書怎麼說:架構決策「難改、影響全域、講究取捨」(例如要不要拆成多個行程);設計決策「好改、影響局部」(例如某個函式怎麼寫)。判斷標準是一句話——「改這個決定有多痛?」愈痛,愈偏架構。

在本專案,「fetch 要在瀏覽器裡執行」是架構決策:它決定了整個系統得拆成三個行程、要寫一個瀏覽器擴充功能。而「oe_ask 回傳的 JSON 欄位怎麼命名」是設計決策,隨時能改。這頁只談前者。


四個維度

書把「架構」拆成四件可以分開討論的事。逐一來看。

維度 1:架構特性(Architectural Characteristics)

書怎麼說:架構特性就是那些以 -ility 結尾的詞——可用性、可維護性、安全性、可擴展性……書的關鍵提醒是:你不可能每個都拿滿分,必須挑出「真正驅動這個系統」的少數幾個(通常 3~7 個),其餘的就接受它普通。

本專案真正的驅動特性只有三個:

特性 白話 在本專案
可行性 / 真確性 「這招到底行不行得通」 OpenEvidence 有 DataDome 機器人偵測,一般伺服器請求會被擋。唯一穩定通過的,是帶著真實登入 session 的請求——這是整個架構的第一因
可共享性 「一份資源多人/多程序共用」 同時開好幾個 Claude / Codex session,不該各自要求登入。於是 relay daemon 常駐、獨占 port 8787,所有 session 共用同一個登入分頁
簡單性 / 安全性 「少一個要顧的東西」 不想管理 API key、也不想把憑證外送。做法:無 API key,瀏覽器登入就是唯一憑證,擴充功能只跟 openevidence.com127.0.0.1 講話

同樣重要的是「沒選上的特性」:本專案刻意不追求「零依賴部署」(它明擺著需要瀏覽器開著)和「headless 自動化」。書一直強調——沒被列為驅動特性的地方,就是你允許自己做普通的地方。

維度 2:邏輯元件(Logical Components)

書怎麼說:把系統切成幾塊「各司其職」的元件。切得好不好,看兩個詞:**內聚(cohesion)**=一塊裡面的東西是不是真的相關;**耦合(coupling)**=塊與塊之間綁多緊。目標是「高內聚、低耦合」。

本專案切成三塊,每塊職責單一:

元件 位置 只做一件事
MCP server src/server.ts 對 MCP client 暴露 oe_* 工具(stdio)
Relay daemon src/relay-daemon.ts 擁有 port 8787,在 server 與擴充功能之間轉遞訊息
瀏覽器擴充功能 extension/ 一個通用的「已認證 fetch 代理」——它不懂 OpenEvidence 業務,只在真實分頁裡執行 fetch

注意擴充功能的高內聚:它被設計成「認證 fetch 代理」這個乾淨抽象,而不是「OpenEvidence 專用擴充功能」。所有業務語意(oe_ask、文章解析)都留在 MCP server。好處是——這個擴充功能可以整個搬去別的專案重用。書的說法:內聚度決定一個元件搬不搬得走。

維度 3:架構風格(Architectural Style)

書怎麼說:架構風格是一些「整體形狀」的樣板。大致分兩類——單體式(分層、模組化單體、微核心/外掛、管線)和分散式(事件驅動、微服務、服務式、空間式)。每種風格對不同「架構特性」擅長程度不同,書用星等表比較。沒有最好的風格,只有最適合你那幾個驅動特性的風格。

本專案是分散式的(三個獨立行程),而且在中間放了一個中介者(broker):relay daemon。

MCP client → MCP server ──▶ relay daemon ──▶ 擴充功能 ──▶ 已登入的 OE 分頁
              (stdio)        127.0.0.1:8787    (執行 fetch)

為什麼願意付「分散式」的代價(多行程、要協調誰先啟動、誰掛了怎麼辦)?答案回到維度 1:「真確性」逼你把 fetch 放進瀏覽器,發問的人和執行的人註定不同行程;「可共享性」又逼你在中間擺一個常駐 broker,否則每個 session 都得自帶連線。所以——風格不是挑出來的,是被驅動特性逼出來的。

維度 4:架構決策(Architectural Decisions)

書怎麼說:重要決策要寫成 ADR(Architecture Decision Record),格式很簡單:Context(背景)→ Decision(決定)→ Consequences(後果,好與壞都要寫)。寫下來的目的,是讓未來的人知道「當初為什麼」,而不是只看到結果。

本專案幾個關鍵決策,補成 ADR 大致是:

  • ADR:用瀏覽器內 fetch,而非 headless / Playwright Context:DataDome 會擋伺服器端與 headless 請求。Decision:請求在使用者真實分頁內執行。Consequences:(+)穩定通過偵測、不用維護 cookie 檔;(-)瀏覽器必須開著、要裝擴充功能。
  • ADR:relay 為常駐共用 daemon,而非每 session 各自啟動 Consequences:(+)一次登入、多 session 共用;(-)成為單點故障——故需自動啟動與健康檢查。
  • ADR:無 API key,瀏覽器 session 即唯一憑證 Consequences:(+)零祕密管理、憑證不外送;(-)session 過期時所有呼叫一起失效。

兩條定律

第一定律:一切都是取捨

書怎麼說:「Everything in software architecture is a trade-off.」——任何架構優點都有對應的代價,看不到代價通常只是還沒找到。

把本專案三個核心決策的帳攤開:

我們買到的 我們付出的
穩定繞過 DataDome(真實 session) 綁死使用者的瀏覽器:要開著、要裝擴充功能
一次登入、任意數量 session 共用 relay daemon 成為單點故障
零 API key、憑證不外送 session 過期時全體呼叫一起失敗

架構好不好,不是「有沒有缺點」,而是「這些缺點是不是你在這個情境下願意付的價」。對「個人在自己已登入的瀏覽器上查 EBM」這個情境——願意。

第二定律:Why 比 How 重要

書怎麼說:「Why is more important than how.」——半年後回頭看,你會忘記「怎麼做的」(那看程式碼就好),但會需要知道「當初為什麼這樣決定」。ADR 存在的意義就是保存這個 why。

本專案的 why 一路寫在 Home 的架構圖、Plan and Tech Debt 的取捨、以及 Lessons and Gotchas。這頁則把散落的 why 收攏成一個架構故事。


帶得走的一課

如果只從這個 repo 帶走一個觀念:先找出真正驅動的那 2~3 個架構特性,其餘一切(風格、元件邊界、部署形態)都是它們的推論。 openevidence-mcp 的「三行程 + 中介 daemon」乍看複雜,但一旦承認「真確性」與「可共享性」不可退讓,這個形狀幾乎是唯一解。

延伸閱讀