翻新現有架構並加入可演化性,很有挑戰性。如果開發人員從未在架構中建置過簡易變更,那麼這種變更就不太可能自發出現。無論多麼有才華的架構師,都無法在不付出巨大努力的情況下將一個Big Ball of Mud(大泥球)轉變為現代微服務架構。幸運的是,透過在現有架構中加入一些彈性點,專案可以在不改變整個架構的前提下獲得好處。
移除非必要的變異性
Continuous Delivery(持續交付)的目標之一是穩定性(stability),也就是以已知的良好部分為基礎,繼續建構。這一目標的常見表現形式是現代DevOps對建置不可變基礎設施(immutable infrastructure)的看法。
現代DevOps用不可變的基礎設施(immutable infrastructure)取代了雪花(snowflakes),從而區域性解決了動態平衡問題。雪花基礎設施(snowflake infrastructure)代表的是由營運人員手工製作的資產,所有未來的維護工作都由人工完成。不可變的基礎設施是指完全以程式化的方式定義的系統。對系統的所有更動都必須透過原始碼進行,而不是修改執行中的作業系統。因此,從營運的角度來看,整個系統都是不可變的:一旦系統啟動,就不會再發生其他變化。
雖然不變性聽起來似乎是可演化性的對立面,但事實恰恰相反。軟體系統由數以千計的互動部分組成,所有的這些元件之間都存在緊密的依存關係。遺憾的是,開發人員仍在為這些元件中的某一個發生變化所帶來的意外副作用而苦惱。藉由鎖定意外變化的可能性,我們可以控制更多導致系統脆弱的因素。開發人員努力用常數取代程式碼中的變數,以縮簡變化的維度。DevOps將這一概念引入營運工作,使其更具宣告性(declarative)。
不可變的基礎設施遵循我們「去除不必要的變數」的建議。建置不斷演化的軟體系統意味著要盡可能控制越多的未知因素越好。要建置能夠預測作業系統最新的服務套件(service pack)會如何影響應用程式的適應性函數,幾乎是不可能的。取而代之,每次執行部署管線時,開發人員都要重新建置基礎設施,盡可能積極地捕捉破壞性變更。如果開發人員能夠從需要考量的要素中,移除像作業系統這種可變但已知的基礎組成部分,他們就能減輕持續測試的負擔。
架構師可以找到各種途徑將可變事物轉換為常數。許多團隊還將不可變基礎設施的建議推廣到開發環境中。有多少次,某個團隊成員感嘆:「但它在我的機器上就能執行!」?確保每個開發人員都擁有完全相同的映像,眾多不必要的變數就會消失。舉例來說,大多數開發團隊都會透過儲存庫(repositories)自動更新開發程式庫,但IDE等工具的更新呢?將開發環境捕捉為不可變的基礎設施,開發人員就可以始終在相同的基礎上工作。
優先選擇可演化而非可預測
未知的未知數是軟體系統的剋星。許多專案在開始時都會列出一系列已知的未知數(known unknowns):開發人員知道他們為此必須學習有關的領域知識和技術。然而,專案也會受到未知的未知數(unknown unknowns)之影響:沒有人知道會出現,但卻意外現身的東西。這就是為什麼所有的「Big Design Up Front(大規模預先設計)」軟體專案都會受挫:架構師無法針對未知的未知因素進行設計。
雖然沒有一種架構能在未知中生存,但我們知道,動態平衡會使得可預測性(predictability)在軟體中變得無用。取而代之,我們更傾向於在軟體中建置可演化性(evolvability):如果專案可以輕易整合變化,那麼架構師就不需要水晶球了。架構不是一項單純的前期活動:專案在其整個生命週期中會以明確或意想不到的方式不斷發生變化。為了隔絕變化帶來的影響,開發人員經常會使用反腐層(anticorruption layer)作為一種常見的保護措施。
建立反腐層
專案經常需要將自身與提供附帶基礎設施的程式庫耦合在一起,如訊息佇列、搜尋引擎等。Abstraction Distraction反模式描述一種情況,即一個專案自身與外部程式庫(無論是商業程式庫還是開源程式庫)有過多「連接」。一旦開發人員需要升級或更換程式庫時,使用該程式庫的大部分應用程式碼都會有基於之前程式庫抽象層的假設。領域驅動設計(domain-driven design)包含一種防止這種現象發生的保護措施,稱為「反腐層(anticorruption layer)」。這裡有個例子。
敏捷架構師在做決策時推崇「最後負責時刻(last responsible moment)」原則,用來應對專案中常見的過早購入複雜性的危險。我們為一家從事汽車批發銷售的客戶,斷斷續續地開發了一個Ruby on Rails專案。應用程式上線後,出現了一個意想不到的工作流程。使用者需要一種方式來起始上傳動作,然後透過一些UI機制(如進度列)來了解進度,或者稍後再回頭檢視批次處理是否完成。翻譯成專業術語就是,他們需要非同步上傳(asynchronous upload)。
訊息佇列(message queue)是解決此問題的一種傳統架構方案,團隊討論是否要將某個開源佇列新增到架構中。許多專案在此關頭的一種常見陷阱是這樣的態度:「我們知道我們最終會需要一個訊息佇列來處理很多事情,所以我們現在就去買一個最豪華的佇列,然後再慢慢學著善用它」。這種做法的問題在於技術債(technical debt):專案中本不該存在的東西,卻妨礙了本該存在的東西。大多數開發人員都把難以維護的舊程式碼視為技術債的唯一形式,但專案也可能在無意中經由過早的複雜性買入技術債。
對於此專案,架構師鼓勵開發人員尋找比較簡單的方式。有位開發人員發現了BackgrounDRb,這是一個非常簡單的開源程式庫,可以模擬由關聯式資料庫所支援的單一訊息佇列。架構師知道這個簡單的工具可能永遠無法擴充到未來的其他問題,但她沒有其他反對意見。她沒有試圖預測未來的使用情況,而是將其置於API之後,使其相對容易替換。
在一週年紀念日前後,出現了第二個非同步需求,是與銷售有關的定時事件。架構師對情況進行了評估,認為使用第二個BackgrounDRb實體就足夠了,於是將其安裝到位,並繼續工作。在兩週年左右,出現了第三個需求,要求不斷更新快取和摘要等值。團隊意識到,當前的解決方案無法處理新的工作量。不過,他們現在對應用程式需要什麼樣的非同步行為有了很好的認識。於是,專案轉向了Starling,這是一個簡單但更為傳統的訊息佇列。由於最初的解決方案被隔離在一個介面之後,兩名開發人員只用了不到一次迭代(在那個專案上花了一週時間)就完成了轉換,而且沒有影響到在該專案上工作的其他開發人員。
因為架構師已經設置了一個帶有介面的反腐層,因此更換功能就成了機械性的工作。建立反腐層可以鼓勵架構師思考他們需要從程式庫獲得什麼樣的語意(semantics),而不是特定API的語法(syntax)。但這並不是為所有東西建立抽象層(abstract all the things)的藉口!有些開發社群喜歡搶先使用抽象層,甚至到了模糊焦點的程度,但當你必須呼叫一個Factory來獲取對某個Thing的一個遠端介面的一個proxy時,事情就會變得難以理解。幸運的是,大多數現代語言和IDE都允許開發人員及時(just in time)抽取出介面。如果一個專案發現自己繫結到了一個需要改變的過時程式庫,IDE就可以代表開發人員提取介面(extract an interface),從而形成一個Just In Time(JIT,剛好及時)的反腐層。
控制應用程式中的耦合點,特別是與外部資源的耦合點,是架構師的關鍵職責之一。試著找出新增依存關係的實際時機。作為一名架構師,請記住依存關係會帶來好處,但同時也會帶來限制。請確保更新、依存關係管理等方面的收益大於成本。(本文摘錄整理自《建立演進式系統架構》第7章,碁峰資訊提供)
圖片來源/碁峰資訊
書名 建立演進式系統架構:支援常態性的變更 第二版(Building Evolutionary Architectures, 2nd Edition)
Neal Ford, Rebecca Parsons, Patrick Kua, Pramod Sadalage/著;黃銘偉/譯
碁峰資訊出版
定價:580元
作者簡介
Neal Ford
Neal Ford是Thoughtworks公司總監、軟體架構師和「迷因牧人(meme wrangler)」。圖片來源_Thoughtworks
Rebecca Parsons
Rebecca Parsons博士是Thoughtworks的CTO。圖片來源_Thoughtworks
Patrick Kua
Patrick Kua是擁有20多年經驗的資深技術領導者。圖片來源_Thoughtworks
Pramod Sadalage
Pramod Sadalage是Thoughtworks公司Data and DevOps總監,負責在資料庫專業人員和應用程式開發人員之間架起橋樑。圖片來源_Thoughtworks
熱門新聞
2024-12-03
2024-11-20
2024-11-29
2024-12-02