《整潔架構實戰》Podcast 準備稿:捷徑、邊界與架構決策
書名: 整潔架構實戰(Get Your Hands Dirty on Clean Architecture) 作者: Tom Hombergs 系列: 設計品味系列 公開標題建議: 設計品味系列|整潔架構實戰.最終回:有意識地走捷徑、守住邊界,以及那句「看情況」 (6/6) 涵蓋範圍: 第 11 章「有意識地走捷徑」、第 12 章「強制架構邊界」、第 13 章「管理多個限界上下文」、第 14 章「以元件為基礎的方法」、第 15 章「決定架構風格」
背景速覽
最後一集,五章一次收尾——而這五章正好構成全書的「成熟篇」:當你已經會蓋這套架構之後,怎麼跟現實妥協(走捷徑)、怎麼防止它腐爛(強制邊界)、怎麼應付更大的系統(多個限界上下文)、有沒有更輕量的起點(元件架構),以及最終那個所有架構書都逃不掉的問題——到底什麼時候該用這套東西。
前五集都在教「正確做法」,這集作者親手把「正確做法」拿來質疑、鬆綁、甚至推翻。它最大的價值是把整本書從「教條」拉回「判斷」——架構不是一套照抄的規則,而是一連串「看情況」的權衡。這集適合用比較哲學、比較收斂的語氣來錄,當成整個系列的總結。
一句話重點
架構的成熟,不在於永遠遵守「正確做法」,而在於先定義並強制一條正確做法,好讓你能在有充分理由時、有意識地偏離它——而選哪種架構風格,最終答案永遠是「看情況」。
值得討論的重點
重點一:捷徑就像破窗——但可以「有意識地」走
第 11 章從一個經典心理學實驗講起:1969 年 Zimbardo 的「破窗理論」。一輛沒車牌的車停在治安差的布朗克斯,24 小時內被拆光;停在富裕的帕羅奧圖則一週沒人碰——直到實驗者自己砸破一扇窗,它隨即遭遇同樣命運。結論用作者的話:一旦某物看起來破敗、無人照管,人腦就覺得「把它弄得更破更爛」是可以接受的。
套到程式碼:在低品質程式庫加一段爛 code 的門檻很低;在充滿捷徑的程式庫再加一條捷徑的門檻很低。所以「從乾淨開始」是工程師的重大責任——因為捷徑一旦滲入,就像破窗,吸引更多捷徑,尤其當接手的是對程式庫毫無感情的後人。
但作者不是要你絕不走捷徑。他承認有時走捷徑就是務實之舉(原型、經濟考量、某段程式碼沒那麼重要)。關鍵是有意識地走、並記錄下來——他推薦用 Michael Nygard 的「架構決策記錄(ADR)」。團隊都知道「這些捷徑是有意識地、為了正當理由而走的」,甚至能降低破窗效應。然後他逐一拆解本書架構裡常見的四個捷徑:使用案例間共用模型、拿領域實體當輸入輸出模型、略過輸入 port、略過 service(讓 persistence adapter 直接實作輸入 port)。每一個都給了「什麼時候是有效捷徑、什麼時候是埋雷」的判準——核心都是同一句:簡單 CRUD 最誘人走捷徑,但 CRUD 常會長成複雜怪獸,所以團隊必須約定「它何時不再是 CRUD」、好在那一刻把捷徑換回正規架構。
重點二:守住邊界的三件武器——可見性、ArchUnit、建置產物
光有目標架構不夠,架構會隨時間侵蝕:邊界弱化、越來越難測。第 12 章給了三件由輕到重的武器來強制「依賴規則」(依賴永遠指向內側):
-
可見性修飾詞(package-private):最基本、最便宜。把 adapter、service 設成 package-private,外界只能透過 public 的 port 介面進來,編譯器就幫你擋掉非法依賴。作者面試愛問「Java 有哪些可見性修飾詞」,很多人答不出 package-private——它其實是組「模組」最有力的工具。缺點:Java 把子套件視為「不同套件」,一旦你想用子套件整理程式碼,package-private 就失靈,被迫改 public。
-
編譯後的適應度函式(ArchUnit):一旦類別變 public,編譯器就不管了,得另尋他法。**適應度函式(fitness function)**就是「一個以架構為輸入、判定它在某面向是否『適應』的函式」——這裡「適應」就是「依賴規則沒被違反」。Java 用 ArchUnit 寫成測試,放進 CI 跑,違規就讓測試失敗。作者也誠實提醒陷阱:如果你把套件名拼錯、或重構時改了套件名,測試會「找不到類別所以找不到違規」,形同虛設——要力求讓這些測試「重構安全」。
-
建置產物(build modules):最重的武器。把每層/每模組做成獨立的 build module(各自的 JAR),在建置腳本裡只宣告架構允許的依賴。非法依賴的類別根本不在 classpath 上,直接撞編譯錯誤。額外好處有三:杜絕循環依賴、允許模組內隔離變更、讓「新增依賴」變成必須在建置腳本明確宣告的有意識行為。代價是要維護建置腳本,所以架構要先夠穩定,才值得拆 build module——拆早了、切錯了,日後反而更難改。
重點三:六邊形管不了應用「內部」的邊界——多個限界上下文怎麼辦
第 13 章戳破一個容易誤會的點。面對多個領域(DDD 的「限界上下文」),直覺反應是「每個限界上下文包成一個自己的六邊形」。作者說:別這樣,這擴展性極差——n 個上下文之間的潛在依賴數是 (n-1)+(n-2)+...+1,每條依賴都要一個 adapter + 一組 port + 一次模型對映,很快變成苦差事,團隊就會抄捷徑繞過它,得到一個「長得像六邊形、卻沒有它好處」的架構。
關鍵釐清:回到六邊形的原始文章,它的本意從來是封裝「一個應用」,而不是封裝「一個限界上下文」——一個應用可以包含多個、或零個限界上下文。所以結論很乾脆:六邊形架構並未、也不必為「同一應用內管理多個限界上下文」提供可擴展解法。在六邊形「之內」,你愛怎麼做都行,該去 DDD 取經來解耦上下文。作者給的做法:互不溝通的上下文各用專屬輸入輸出 port(強烈建議至少讓「持久化輸出 port」彼此分離,每個上下文有自己的持久化,否則共用資料模型會強耦合、日後想抽成微服務會極痛苦);需要適度溝通時,用「只傳必要資料(傳 user ID 而非整個 user)」、「領域事件」、或「應用服務當協調者兼交易邊界」。
重點四:更輕量的起點——元件架構,以及終極答案「看情況」
第 14 章給「覺得六邊形太難、太重」的人一個更簡單的起點:以元件為基礎的架構(component-based architecture)。它簡單到只有四條規則——元件有專屬命名空間、有自己的 API 與內部(internal)、API 可從外部呼叫但內部不可、元件可包含子元件。作者用一個真實的 Kotlin「check engine」網頁爬蟲元件示範,拆成 queue / database / checkrunner 三個子元件,每個把「存取外部系統的技術細節」藏進 internal 套件——跟六邊形的輸出 adapter 異曲同工。
它的強制也很優雅:一條 ArchUnit 適應度函式——「internal 套件外的類別,不准存取 internal 套件內的類別」。這條規則比第 12 章那條「穩定得多」,因為它不必為每個用到的函式庫加例外,也不必知道架構裡有哪些元件(新增移除元件都不用改測試)。元件架構天生模組化,可以先從它起步、需要時再演進成六邊形或其他風格。
最後第 15 章收束全書,也收束整個系列。作者花了太久才領悟的一課:軟體架構不是專案開頭定好就會自己照顧自己的東西,它應該隨時間演進、甚至中途換風格。所以唯一該做的是埋下「可維護性」的種子——讓程式碼模組化、邊界清晰。然後給判準:「若領域程式碼不是你應用中最重要的東西,你大概不需要這種架構風格」;一堆 CRUD 起步用元件架構就好,等它長成領域豐富的應用,六邊形才成為好選擇。他甚至說六邊形這類以領域為中心的架構是「DDD 的促成者」——沒有它,DDD 落不了地。而對「該選哪種架構」這個終極問題,作者給的答案,就是顧問那句永恆的——「看情況(It depends)」:取決於軟體類型、領域角色、團隊經驗,最終取決於你自己對某個決定是否自在。要培養這種判斷,唯一的路是累積對不同風格的實際經驗——挑個小模組試試,發展出讓你自在的風格。
注意事項
⚠️ 「有意識地走捷徑 + 用 ADR 記錄」是個漂亮的理想,但現實裡 ADR 常常寫了沒人讀、或根本沒人寫。「有意識」這三個字很容易變成事後合理化的遮羞布——當下偷懶,事後說「我是有意識決定的」。真正讓捷徑「有意識」的,不是寫不寫文件,而是「有沒有一條被強制的正確做法當對照基準」。沒有第 12 章的邊界強制,你根本不知道自己在偏離什麼,所謂「有意識的捷徑」就只是普通的爛 code 加一句好聽的話。
⚠️ 三件邊界武器要按「架構穩定度」拾級而上,別一開始就上最重的。新專案架構還在劇烈變動時就拆 build module,等於把水泥灌進還沒定型的結構——作者自己警告「build module 一旦選定就傾向沿用、切錯也較不會修正」。務實順序是:一律用 package-private 打底 → 架構初步成形後加 ArchUnit → 真的非常穩定、且有跨團隊/跨倉庫需求時,才拆 build module。
⚠️ 「每個限界上下文都該有自己的持久化」是很強的解耦,但對一個還在驗證 product-market fit 的早期單體,硬要把資料庫切成多 schema、禁止跨上下文 join,可能是過早優化——你連邊界劃在哪都還沒想清楚,就先付了分離的稅。作者的建議成立於「你已經相當確定邊界、且預期未來要抽微服務」。邊界沒想清楚就硬切,切錯的成本比不切還高。
⚠️ 全書(尤其這最後一集)反覆的「看情況」「沒有銀彈」「為每個 case 個別決定」,對資深工程師是智慧,對新手卻可能是災難——它把所有判斷責任丟回給「你的經驗」,而新手恰恰沒有經驗。所以這集對聽眾要誠實:「看情況」不是讓你擺爛不決定,而是「先照書上的正確做法做、做出感覺、累積經驗,再來談偏離」。沒有經驗之前,預設遵循紀律,比過早「看情況」自由發揮更安全。
專家補充
💡 「適應度函式(fitness function)」這個詞不是 Hombergs 發明的,它來自 Neal Ford、Rebecca Parsons、Patrick Kua 的《Building Evolutionary Architectures》(演進式架構)。那本書的核心主張跟本書第 15 章完全共振——架構不是一次定生死,而是要設計成「可演進」的,並用適應度函式持續守護你在意的架構特性(依賴方向、效能、安全…)。ArchUnit 就是「依賴方向」這個適應度函式的具體工具。這集可以把這兩本書串起來:本書是「演進式架構」思想在一個具體 Java 架構上的實踐。
💡 「六邊形封裝的是『一個應用』而非『一個限界上下文』」這個釐清,化解了業界一個超常見的誤用。很多團隊一聽 DDD + 六邊形,就給每個 aggregate 套一個六邊形,結果 port/adapter 多到爆炸、對映寫到死。作者把話講白:那不是六邊形的本意。這個洞見能幫聽眾省下大量無謂的架構工——限界上下文之間的解耦該用 DDD 的工具(領域事件、限界上下文映射),不是無腦複製六邊形。
💡 第 13 章「每個限界上下文有自己的持久化、用領域事件鬆耦合溝通」,其實已經把單體一步步推向微服務的形狀了。這本書表面在講單體內的架構,但它教的「劃乾淨邊界、持久化分離、事件驅動溝通」,正是「模組化單體(modular monolith)」的精髓——而模組化單體被越來越多人認為是比「一開始就微服務」更務實的起點(Sam Newman、Simon Brown 都這麼主張)。可以把這集接到「先把單體模組化、邊界穩了再考慮拆服務」這個現代共識。
💡 「看情況」聽起來像廢話,但它其實是專家與新手的分水嶺。新手要規則(「告訴我該怎麼做」),專家要脈絡(「告訴我各選項的取捨,我自己判斷」)。這呼應 Dreyfus 技能習得模型——從「依規則」到「依直覺」的進階。整本書(乃至整個系列)其實是在做這件事:先給你規則(前幾集),再教你質疑規則、依脈絡判斷(這集)。所以「看情況」不是逃避,是把聽眾從「照抄者」往「判斷者」推的刻意設計。
💡 Uncle Bob 的同心圓常被批評「太理論、不知道 Java 怎麼落地」,而 Hombergs 這本最大的貢獻就是補上這一刀。但反過來,這本書也有它的邊界——它聚焦後端 Web 應用、聚焦 Java/Spring,對前端架構、對 event-driven/streaming 系統、對 serverless 著墨不多。誠實地把這個邊界講給聽眾,反而能讓「看情況」更具體:這套架構最適合的甜蜜點,是「領域複雜、長壽命、團隊會輪替的後端業務應用」。不在這個甜蜜點裡,就真的要「看情況」了。
討論問題
🎙️ 你的程式庫現在有沒有「破窗」——某個大家都知道很爛、卻因為「反正已經這樣了」而不斷被往上加爛 code 的地方?那扇窗是什麼時候、被誰砸破的?
🎙️ 你上一次走捷徑,是「有意識地、知道自己在借債」,還是「當下偷懶、事後才說自己有意識」?你們團隊有沒有任何形式的 ADR 或捷徑記錄?如果沒有,那些捷徑現在還有人記得為什麼嗎?
🎙️ 你的架構靠什麼守邊界——純靠 code review 和自律,還是有 ArchUnit 這種「違規就讓建置失敗」的自動化護欄?如果只靠自律,它在上一個 deadline 撐住了嗎?
🎙️ 你有沒有看過「每個 aggregate 包一個六邊形」這種過度套用?或者反過來,一個該拆限界上下文的系統,卻所有領域的程式碼黏成一團爛泥?
🎙️ 面對「我的專案到底該不該用這套架構」——用作者的判準問自己:你的領域程式碼,真的是你應用中最重要的東西嗎?還是它其實大半是 CRUD,你只是想用個時髦架構?
更大範圍關聯
-
接 Uncle Bob《Clean Architecture》:整個系列在這裡收尾,正好回望——Uncle Bob 給世界觀與同心圓,Hombergs 給 Java 落地與「何時別用」的務實判斷。兩本合起來,才是完整的「整潔架構」。
-
接《Building Evolutionary Architectures》(Neal Ford 等):「適應度函式」「架構應可演進、中途可換風格」直接源自這本。本書第 12、15 章是演進式架構思想的具體實踐。
-
接 DDD(Eric Evans / Vaughn Vernon):限界上下文、領域事件、限界上下文映射、應用服務當協調者——第 13 章幾乎是 DDD 戰略設計的濃縮。作者明說六邊形是「DDD 的促成者」。
-
接「模組化單體 vs 微服務」之爭(Sam Newman、Simon Brown):第 13、14 章教的「劃邊界、持久化分離、事件溝通、元件模組化」,正是模組化單體的核心,也是「先別急著微服務」這個現代共識的技術基礎。
-
接破窗理論(Wilson & Kelling)與《The Pragmatic Programmer》:那本書也用破窗理論談「別留著沒修的破窗」(軟體熵)。本書把它正式接到架構捷徑的管理上。
錄製建議
目標約 22 分鐘(收尾集可略長),單人主講。建議節奏:
- 0–2 分:開場 + 系列回顧。「最後一集,五章一起收。前五集教正確做法,這集作者親手來鬆綁、質疑、甚至推翻它」。
- 2–7 分:第 11 章捷徑。用破窗理論(那輛車的故事)開場最有畫面,落到「從乾淨開始的責任」+「有意識地走捷徑、用 ADR 記錄」,快帶四個捷徑的共同判準(CRUD 會長成怪獸)。
- 7–11 分:第 12 章三件邊界武器。由輕到重:package-private → ArchUnit 適應度函式 → build module,強調「按架構穩定度拾級而上」。
- 11–15 分:第 13 章。戳破「每個限界上下文包一個六邊形」的迷思——六邊形封裝的是「應用」不是「上下文」,該用 DDD 解耦,並強調「每個上下文自己的持久化」。
- 15–18 分:第 14 章元件架構(更輕量起點,四條規則,那條更穩定的適應度函式)+ 第 15 章收尾(架構會演進、可換風格、「領域不是最重要就別用這套」)。
- 18–22 分:總結整個系列 + 那句「看情況」。誠實講「看情況不是擺爛,是先照紀律做、累積經驗再談偏離」,把整本書從教條拉回判斷。最後給系列收尾語。
口氣提醒:這是收尾集,語氣可以比前幾集更沉、更哲學一點。核心情緒是「從照抄規則,長成會判斷的工程師」。破窗故事和「看情況」是頭尾兩個記憶點,都要講透。別讓五章變成趕進度的流水帳——寧可少講細節,也要把「成熟 = 有意識地偏離」這個主線講清楚。