架構治理基石:基于規范 + 模式的工具化
圍繞于 ArchGuard,我們一直在探索適合于大多數企業的治理模式。通常來說,對于應用架構的治理來說,我們的預期目標是,對應的 架構設計 (廣義上的)能被采納和遵守。如果過程中出現有流程上的問題,導致了架構在實施過程中,架構會不斷偏離預期的設計。那么,我們就會致力于匹配設計相應的規范、規則和函數,來確保后續在實施過程中是能正確的落地。
也因此,在架構治理上,我們可以用一些簡單的元素來進行概括。
- 模式。尋找壞的味道,并使用好的設計來改進它。
- 規范。一個關于架構決策的文檔化。
- 規則 。規范的工具化與形式化表示
于是乎,在我們的場景下,架構治理方案就可以圍繞于三個要素來構建。
模式:壞的味道與好的方案
在我們的行業里,會將解決特定問題的解決方案稱之為模式,如設計模式、架構模式。這些廣為流傳的編程模式,都是好的、最佳的實踐。但是,就個人而言,而另外一類,不好的模式其實也是模式,不過,我們往往把它們稱為有味道(Smell)的,代碼里的是 代碼壞味道 ,架構里的便是 架構的壞味道 。
在一個組織里,代碼隨著人員的內部流動、自定義框架的編碼風格、公司級別的規范定義,使得整體的代碼模式會趨向于一致。這種一致性會受到人員變更帶來短期的影響,些許的高水平 “新人” 可能會帶給團隊一股新鮮備注;大量的新人的涌入,也會可能使得原來的好的模式被沖淡。但是呢,不論如何,替換的只是模式本身,而不是模式的存在。而壞味道本身即是與好的模式進行比較,即好的實踐應該是怎樣的。
也因此,在治理的第一步就是讓壞味道能浮出來。它可以是通過人為地看項目代碼,進而得到一些初步的結論,并基于結論構建出洞見;也可以是像 ArchGuard 一樣的專家系統,可以通過 AST 從語法中分析到壞的味道,并將它們可視化出來。
規范:架構決策的文檔化
規范是我們在日常的開發過程中約定俗成的標準,其本質是對于一系列架構決策的文檔化。作為架構師/開發者,我們定義所有的 API 應該是怎樣的?如何去處理數據?如何構建質量防護?在另種一個話題: 輕量級架構決策 里,我們定義的是架構決策應該編寫出來,以格式化的文檔。
好的規范的本質是 推薦 一系列的 最佳實踐 。“年輕” 的開發者往往不能理解諸多實踐的意義,為什么它應該這么做?不這么做會影響到什么?有時候,需要經驗豐富的開發者告他們,WHY + WHAT + HOW。不過呢,在一些大型 IT 組織的里,人們往往依舊會采用 “考試” 的方式,用一種簡單粗暴的方式來確保:對于什么是好的模式/實踐認知是一切的。
而規范不論是明文規定,還是約定俗成,我們都可以發現,在業務繁榮或者新的加入的時候,慢慢都會被破壞。所以,我們又開始尋找一些能讓規范有效力的方式。
規則:規范的工具化與形式化表示
規則從某種意義上來說,是一種規范的工具化手段。其最常見的方式是 Linter,一種基于語法樹/語法結構的規則化工具。
這種規則可以是我們在學習英語時的語法規則,它是語言中高度抽象的組合關系和聚合關系的約定俗成的語言的規則,包括組合規則和聚合規則。諸如于在英語中,常見的句型可以是:主語-謂語-賓語-賓語補足語(英語四級沒過,這簡直是噩夢)。圍繞于這些規則,便可以構建一系列的自動化檢測工具。
這樣的工具,也可以是我們使用 Java 編寫企業應用時,用的 Checkstyle;又或者是使用 TypeScript 編寫前端應用時,用的 ESLint。從這一點上來說,它們就是對于常見規則的形式化。
治理:匹配模式,展示問題,規則化與演進
模式、規范、規則都依賴于編寫工具的人,他應該即是一個架構上的專家,又需要精通 編碼 + 語言 本身。又或者是兩者一起進行結對,才能設計一個如此的系統。
回到編程來治理問題上,從過程上來說,我們治理架構問題的方式是:
- 設計、尋找對應的規范(即最佳實踐)
- 人為識別代碼中的模式,隨后通過編寫代碼匹配,即規則。
- 通過可視化 + 分析的方式,展示出代碼中的問題。
- 將規范規則化,并配合上度量指標
- 構建適應度函數,指導系統進行演進。
以 ArchGuard 中的 SQL 規則檢查為例,如下是代碼中的 SQL(經過修改):
override fun getById(systemId: Long): SystemInfo? {
val sql = "select * from system_info where id=:systemId"
return jdbi.withHandle<Long, Nothing> {
it.createQuery(sql)
.bind("systemId", systemId)
.mapTo(SystemInfo::class.java)
.firstOrNull()
}
}
從 SQL 性能等角度來說,這里的 select *? 應該是禁止的。但是呢,從識別的難度來說,它是存在的,我們需要結合著語法分析的結果,即 createQuery 的被調用 + 參數表中對應值的存在,才能將 SQL 從代碼中解析出來。展開來說,在這個案例里,因為想治理的是 SQL,所以我們所做的是:
- 尋找通用的 SQL 規范。
- 結合人為查閱的方式,從 SQL 規范中尋找第一個易于實現的案例
- 編寫代碼,從語法樹抽取 SQL,和對應的 SQL 規則
- 將所有的問題展示到一起
從治理的層面來說,最大的難點在于 模式逃逸 —— 即開發者可能根據識別的模式,修改代碼的實現方式,導致度量無用。不過,這就是另外一個關于度量如何改進的問題。