新系統架構怎麼設計?先做難以逆轉的決策,其他先不要急

新系統架構最重要的,是先辨認哪些決策一旦做錯,之後會很昂貴、很難逆轉,而不是把未來五年的技術全部預先設計好。 邊界、核心領域模型、資料責任與外部依賴通常值得早點想清楚;框架、部署方式、快取、服務拆分等較容易調整的選擇,則應盡量延後到有真實證據再決定。

很多新系統剛開始設計,就會迅速進入技術選型。

要不要微服務?

要不要事件驅動?

要不要多資料庫?

要不要 Kubernetes?

要不要先做訊息佇列?

這些技術都可能有用,但如果產品還沒有真實流量、領域邊界也還沒穩定,太早決定往往只會把未來綁死。

架構設計應該先問:哪些決策值得現在做,哪些反而應該故意晚一點做?

目錄 隱藏

先分清楚:什麼叫「難以逆轉的決策」?

有些決策改起來很便宜。

例如按鈕顏色、某個前端元件、某一頁的版面,通常都可以在幾小時或幾天內調整。

但另一些決策一旦被大量程式碼、資料與團隊流程依賴,成本就會快速上升。

例如:

核心商業規則是否和框架綁死。

付款、訂閱、會員資格由哪個系統負責。

同一個「客戶」在不同業務情境裡到底是不是同一種模型。

所有模組是否共用一個巨大的資料模型。

第三方服務的 API 是否直接滲進核心程式。

這些問題如果一開始完全不處理,未來改動往往牽一髮動全身。

第一個優先決策:核心商業規則要和外部技術分開

Clean Architecture 最有價值的原則,是讓依賴方向指向核心商業規則,而不是讓核心規則去依賴資料庫、框架或第三方服務。

換句話說,真正重要的規則不應該只存在某個 Controller、ORM Model 或外掛 Hook 裡。

例如「付款成功後才能開通課程資格」是一條商業規則。

它不應該等於「某家金流 API 回傳某個欄位後直接寫會員資料」。

更穩定的做法,是核心系統先定義「付款已確認」這個業務事件,再由外層適配不同金流服務。

這樣未來換供應商時,真正要替換的是外層,而不是整個商業邏輯。

如果想更深入理解依賴方向,可以延伸閱讀 Clean Architecture:為什麼商業邏輯不該被框架與資料庫綁死。

第二個優先決策:領域邊界比服務數量重要

領域驅動設計(Domain-Driven Design)裡的限界上下文(Bounded Context),處理的是一件很現實的事:同一個詞,在不同業務區域裡可能有不同意思。

例如「客戶」這個詞。

在銷售情境裡,客戶可能是潛在買家與成交狀態。

在付款情境裡,客戶可能是帳務主體、發票資訊與付款紀錄。

在課程交付裡,客戶又可能是學員、存取資格與學習進度。

如果硬把這三種需求塞進一個超大型 Customer Model,久了就會變成所有模組都互相依賴。

所以新系統要先找出業務在哪裡自然分開,再談要不要拆成微服務。

限界上下文不等於微服務

這是一個很常見的誤解。

限界上下文是一個模型與語言的邊界;微服務則是部署與執行的邊界。

兩者可能對齊,但不必一開始就一對一。

一個新系統完全可以先用模組化單體(Modular Monolith),在同一個應用程式裡保持清楚模組邊界。

等到某個模組真的需要獨立部署、獨立擴充或由不同團隊維護,再考慮拆成服務。

這樣做的好處,是邊界還在學習階段時,調整成本比較低。

第三個優先決策:單體還是微服務,先看你真正需要什麼

Martin Fowler 在談 Monolith First 時提出一個很實際的觀察:很多成功的微服務系統,都是先從單體開始,再逐步拆分。

原因很簡單:服務邊界一旦拆錯,跨服務重構通常比單一程式內重構昂貴得多。

微服務也有明顯成本。

遠端呼叫會失敗。

服務之間要處理網路延遲。

資料一致性變複雜。

部署、監控、追蹤與告警都需要更多成熟度。

因此對一個單一團隊、流量尚未證明的新產品,模組化單體通常是很合理的起點。

只有當獨立部署、團隊自主、局部擴充或故障隔離的價值已經超過分散式系統的成本,微服務才開始有明確理由。

第四個優先決策:資料由誰負責,比選哪個資料庫更重要

新系統常常花很多時間比較 PostgreSQL、MongoDB、Redis、ElasticSearch,卻沒先回答一個更重要的問題:

哪一份資料才是最後依據?

例如付款狀態、訂閱資格、課程存取權與 CRM 標籤都可能同時存在不同系統。

如果沒有明確系統紀錄(System of Record),未來一旦資料不一致,就沒有人知道該信哪一份。

所以資料設計應該先定義責任。

付款結果由誰負責?

會員資格由誰決定?

搜尋索引只是衍生資料,還是也能被修改?

統計報表延遲幾分鐘可以接受嗎?

這些決策通常比「哪個資料庫比較熱門」更重要。

如果要理解複寫、交易與資料一致性的取捨,可以延伸閱讀 Designing Data-Intensive Applications:大型資料系統真正難的是什麼。

資料庫選擇應該跟著工作負載,不要跟著流行

資料模型與儲存方式需要配合實際使用情境。

關聯資料、文件資料、圖形關係、全文搜尋與分析查詢,各自需要的能力不同。

很多系統一開始其實用一個成熟的關聯式資料庫就足夠。

只有當第二種讀取模式變得重要,例如搜尋、分析或高頻快取,才需要評估是否加入額外的衍生資料系統。

每多一種資料庫,就多一套備份、權限、監控、同步與故障處理。

所以多資料庫不是免費彈性。

第五個優先決策:外部依賴一定會失敗,先決定怎麼退化

只要系統會呼叫金流、Email、AI API、物流、簡訊或其他外部服務,就已經有分散式系統問題。

AWS Builders’ Library 很強調一件事:任何跨服務呼叫都可能失敗。

而且最麻煩的不一定是直接回錯誤。

有時候是一直沒有回應。

如果沒有設定逾時(Timeout),一個卡住的依賴可能長時間占住連線、執行緒與資源,最後把原本局部問題放大成整個系統停住。

因此外部依賴至少要先想清楚:

多久沒回應就算失敗?

可以重試嗎?

重試會不會造成重複付款或重複寄信?

依賴服務掛掉時,核心流程能不能繼續?

恢復之後如何補處理?

重試不是越多越可靠

重試(Retry)看起來是最簡單的容錯方法。

第一次失敗,再試一次就好。

但如果下游服務本來就是因為過載才失敗,所有上游同時重試,只會增加更多壓力。

所以常見做法會搭配指數退避(Exponential Backoff)與抖動(Jitter),讓重試不要集中在同一個時間點。

另外,重試前還要確認操作是否具備冪等性(Idempotency)。

查詢重做一次通常沒問題;付款扣款如果重做兩次,就完全不同。

這也是架構設計裡很值得早點決定的邊界。

第六個優先決策:哪些東西一定要強一致,哪些可以晚一點同步

不是所有資料都需要即時一致。

例如網站瀏覽數晚幾分鐘更新,通常沒有大問題。

搜尋索引稍微延遲,也常可以接受。

但付款、庫存、會員權限與訂閱資格,就可能需要更嚴格的保證。

如果所有資料一律要求最強一致性,系統成本可能很高。

如果所有地方都接受最終一致性(Eventual Consistency),又可能讓核心商業規則出錯。

所以應該逐一問:

這個資料錯五分鐘會發生什麼?

同時有兩個人修改會發生什麼?

重複處理會發生什麼?

這些答案比技術名稱更能決定架構。

第七個優先決策:先估算真實負載,再決定要不要擴充

新系統常常為「未來可能有一百萬使用者」設計。

但如果第一年只有幾千名活躍使用者,很多昂貴架構根本沒有必要。

最簡單的方式,是先做量級估算。

每天活躍人數多少?

每人一天大概做幾次主要操作?

尖峰是平均的兩倍、三倍,還是十倍?

每天新增多少資料?

哪些查詢最重?

這些數字算出來後,很多時候會發現:一台正常規格的應用伺服器加一個有索引的資料庫,就已經能支撐很長一段時間。

架構要替未來保留選擇

好的架構不是預先知道所有需求。

沒有人做得到。

比較實際的目標,是讓容易變動的部分不要滲透進核心。

例如金流供應商可能會換,就用自己的介面包住。

搜尋引擎可能會換,就把搜尋索引視為衍生資料。

AI 供應商可能會換,就不要把某家的 SDK 型別直接放進商業規則。

這樣未來改動時,成本會集中在邊界,而不是散在整個系統。

哪些事情反而應該故意延後?

很多技術決策其實不需要第一天就定死。

例如:

是否要多區域部署。

是否要把單體拆成十幾個服務。

是否要導入事件溯源。

是否需要專用訊息佇列。

是否要支援多租戶複雜隔離。

是否要為數百萬人做分片。

這些事情可以等到真實需求、團隊規模與瓶頸出現時再決定。

過早設計未來,會把現在的開發速度、除錯難度與維運成本一起拖高。

可逆決策與不可逆決策,可以用一張表來分

每次架構討論時,可以把決策分成兩類。

難逆轉:領域邊界、核心資料責任、外部供應商是否滲入核心、關鍵交易保證、服務之間的資料所有權。

較容易逆轉:前端框架、某個快取策略、某項監控工具、某一個部署參數、局部實作方式。

難逆轉的決策需要更多證據與紀錄。

容易逆轉的決策則應該快速、簡單,不要開十次會議。

決策紀錄:把重要選擇和理由留下來

架構真正麻煩的地方,不只是決策本身,而是半年後沒有人記得當初為什麼這樣選。

所以重要架構決策可以留下簡單的決策紀錄(Decision Log)。

至少記三件事:

決定了什麼。

為什麼這樣決定。

當時排除了哪些替代方案。

例如:

「目前採用模組化單體,不拆微服務;原因是只有一個團隊、流量尚小、邊界仍在變動;重新評估條件是出現獨立部署需求或某模組需單獨擴充。」

這種紀錄不需要很長,卻能讓未來團隊知道當時是有意識地延後拆分。

架構最容易失控的地方,是把「可能」當成「一定」

可能有大量流量。

可能進軍海外。

可能要支援幾百家企業。

可能要換資料庫。

可能未來有十個團隊。

每一個「可能」都能產生一個新的抽象層、一個新服務、一套新工具。

最後系統還沒有使用者,團隊已經在維護一個想像中的大型公司架構。

真正穩健的架構,會替未來保留空間,但不會為沒有證據的未來先支付全部成本。

如果是課程網站或會員平台,應該先想哪些邊界?

對課程網站來說,一開始不需要複雜微服務。

但可以先把幾個責任想清楚:

訂單由誰負責。

付款狀態由誰判定。

課程存取資格由誰決定。

學習進度由誰保存。

電子報與 CRM 是主要資料,還是同步出去的衍生資料。

外部金流暫時失敗時,訂單要停在哪個狀態。

這些都比「要不要 Kubernetes」更接近真正的架構問題。

新系統架構可以先用這八題檢查

一、核心商業規則能不能在沒有資料庫、網頁框架與第三方 API 的情況下測試?

二、同一個業務名詞在不同情境下,有沒有不同意思?

三、目前真的需要微服務,還是模組化單體就足夠?

四、每一份重要資料的系統紀錄是誰?

五、哪些操作必須強一致,哪些可以延遲同步?

六、每一個外部依賴如果變慢或掛掉,系統會怎麼退化?

七、目前真實負載是多少,第一個瓶頸可能在哪?

八、哪些決策很難逆轉,哪些可以等有證據再決定?

如果這八題能回答清楚,通常已經比畫一張複雜架構圖更有價值。

新系統架構的目標,是讓未來改得動

架構不是炫耀技術選型的地方。

它真正要做的是控制改變成本。

核心規則和外部技術分開,未來換供應商比較容易。

領域邊界清楚,未來拆服務才有自然切點。

資料責任明確,資料不一致時才知道該信哪一份。

外部依賴有逾時、重試與退化策略,局部故障才不會拖垮整個系統。

最重要的是,把少數真正昂貴的決策做好,其他可以延後的事情就保持簡單。

好的架構不需要一次決定完所有未來,它更重要的工作是讓未來還有選擇。

常見問題

新系統一開始就應該用微服務嗎?

通常不需要。單一團隊、邊界尚未穩定、流量也還不大的產品,先用模組化單體通常更容易調整。微服務應該在獨立部署、局部擴充、團隊自主或故障隔離的價值已經明確時再評估。

什麼架構決策最值得一開始就做?

核心商業規則與外部技術的邊界、主要領域模型、資料所有權、關鍵交易保證與外部依賴的失敗處理,通常比框架、快取或部署工具更難逆轉。

模組化單體和一般單體有什麼不同?

模組化單體仍部署成一個應用程式,但內部有清楚模組邊界、資料責任與依賴規則。這能保留單體的簡單,又降低未來模組互相纏繞的風險。

外部 API 一定要做重試嗎?

不一定。要看錯誤類型與操作是否安全重試。查詢通常比較容易重試;付款、建立訂單等會改變狀態的操作,需要先處理冪等性,否則重試可能造成重複副作用。

架構設計要一次規劃到未來幾年嗎?

不建議。可以保留成長路徑,但不需要先做所有未來功能與基礎設施。比較好的做法是把難逆轉的決策想清楚,其他決策保留可逆性,等真實需求與瓶頸出現再擴充。

參考資料

Robert C. Martin. Clean Architecture: A Craftsman’s Guide to Software Structure and Design.
Eric Evans. Domain-Driven Design: Tackling Complexity in the Heart of Software.
Martin Fowler. “Bounded Context.” MartinFowler.com.
Martin Fowler. “Monolith First.” MartinFowler.com.
Martin Fowler and James Lewis. “Microservices.” MartinFowler.com.
Martin Kleppmann and Chris Riccomini. Designing Data-Intensive Applications, Second Edition.
Michael T. Nygard. Release It! Design and Deploy Production-Ready Software.
Amazon Web Services. “Timeouts, Retries, and Backoff with Jitter.” Amazon Builders’ Library.

元氣站長
元氣站長

經營網路事業,賺取持續收入!
網路賺錢修煉,成就理想人生!