架構決策的反模式

架構決策

對架構師的主要期望之一就是做出架構決策。架構決策通常涉及應用程序或系統的結構,但也可能涉及技術決策,尤其是當這些技術決策影響架構特徵時。無論在什麼情況下,一個好的架構決策是能夠指導開發團隊做出正確技術選擇的架構決策。做出架構決策需要收集足夠多的相關信息,證明決策的合理性,記錄決策並將決策有效地傳達給正確的利益相關者。

架構決策反模式

架構決策是一門藝術。不出意外的話,架構師在進行決策時會遇到幾種架構反模式。程序員 Andrew Koenig 將反模式定義爲:那些一開始看起來似乎是一個好主意,但卻會帶來麻煩的模式。反模式的另一個定義是:能夠產生負面結果的可重複過程。在架構決策過程中,可能會(並且通常會)出現的三種主要架構反模式是

這三種反模式通常是一個循序漸進的過程:克服 “掩蓋你的資產” 會導致 “土撥鼠日”,而克服“土撥鼠日” 則導致“電子郵件驅動架構”。做出有效而準確的架構決策需要架構師克服所有這三種反模式。

掩蓋你的資產反模式

嘗試做出架構決策時會出現的第一個反模式是掩蓋你的資產。當架構師由於擔心做出錯誤選擇而避免或推遲做出架構決策時,就會發生這種反模式。

有兩種方法可以克服這種反模式。第一種方法是等到最後的責任時刻( last responsible moment)再完成重要的架構決策。最後的責任時刻意味着一直等到你有足夠的信息來證明和驗證你的決策,但不能等待太久,以避免拖延開發團隊或陷入分析癱瘓( Analysis Paralysis)反模式。第二種方法是持續地與開發團隊協作,以確保你做出的決策能夠按預期實現。這一點至關重要,因爲作爲一名架構師,你無法掌握特定技術的所有細節以及所有與之相關的問題。通過與開發團隊緊密協作,架構師可以在出現問題時及時快速地改變架構決策。

爲了說明這一點,假設架構師決定,對於所有與產品相關的參考數據(產品描述、重量和尺寸),依賴這些數據的所有服務實例都需要對它們進行復制並以只讀的形式存放在自己的緩存當中,數據的主副本歸商品目錄服務所有。複製緩存意味着,如果產品信息有任何更改(或者有新產品被添加),商品目錄服務將更新其緩存,然後需要通過某個複製(在內存中進行)緩存產品,將產品數據複製到需要該數據的所有其他服務當中。做出此決策的理由是減少服務之間的耦合,並有效共享數據,從而無須進行服務間調用。但是,執行此架構決策的開發團隊發現,由於某些服務的一些可擴展性需求,該決策所需要的進程內存比可用內存更大。通過與開發團隊緊密協作,架構師可以快速瞭解問題並調整架構決策來適應這些情況。

土撥鼠日反模式

一旦架構師克服了掩蓋你的資產反模式並開始做出決策,就會出現第二個反模式:土撥鼠日反模式。當人們不知道爲什麼要做出某個決策時,就會發生土撥鼠日反模式,因此它不斷地被反覆討論。土撥鼠日反模式是從比爾 · 默裏( Bill Murray)的電影《土撥鼠日》中得名的,影片中每一天都是 2 月 2 日。

發生土撥鼠日反模式的原因是架構師做出了某個架構決策,但無法爲該決策提供理由(或無法提供一個完整的理由)。在論證架構決策時,爲決策提供技術和業務辯護是非常重要的。例如,架構師可以決定將單體應用分解爲多個單獨的服務,將應用程序的功能解耦,從而使應用的每個部分使用較少的虛擬機資源,並且可以進行單獨維護和部署。雖然這是技術合理性的,但是缺少業務合理性。換句話說,企業爲什麼要在這種架構重構上花錢?本例中,一個好的業務合理性可能是:更快地交付新的業務功能,從而縮短產品上市時間;另一個可能是減少開發和發佈新功能的成本。

ps:經常有人在討論我想在我們家嘗試 DDD,但是得不到研發團隊支持。我想拆分微服務,但是產品經理不理解 balabala...... 你當然可以爲你的 “技術冒險” 找一個理由,但最好的方式是解決問題出發。

論證決策對業務的價值對於任何架構決策都至關重要。這也是一個首先確定是否應該做出架構決策的試金石。如果某個架構決策不能帶來任何業務價值,那麼它可能不是一個好的決策,應該重新考慮。

四個最常見的業務合理性包括成本、上市時間、用戶滿意度和戰略定位。當着眼於這些常見的業務合理性時,考慮對業務利益相關者來說緊要的事情就變得很重要。如果業務利益相關者較少關注成本,而更關注上市時間,此時僅依據節省成本來證明特定的決策可能是不正確的。

電子郵件驅動架構反模式

一旦架構師做出了一系列決策並充分證明了這些架構決策的合理性,就會出現第三個架構反模式:電子郵件驅動架構反模式。電子郵件驅動架構反模式是指因遺棄、忘記或者甚至不知道已經做出的架構決策而無法實現該架構決策。這個反模式講的就是要有效地溝通架構決策。電子郵件是一種很好的交流工具,但它並不是一個好的文檔存儲庫系統。

提高架構決策溝通有效性的方法有很多,通過它們可以很好地避免電子郵件驅動架構反模式。溝通架構決策的第一條規則就是不要在電子郵件正文中包含架構決策,因爲這會爲該決策創建多個系統記錄。很多時候,重要的細節(包括合理性)都不會包含在電子郵件當中,因而會重新陷入土撥鼠日反模式。而且,如果該架構決策被更改或取代,如何讓人們收到修訂後的決策呢?更好的方法是僅在電子郵件正文中提及決策的性質和上下文,並提供架構決策和相應詳細信息的單個系統記錄鏈接(無論它是一個 wiki 頁面還是文件系統中的文檔)。

有效的架構決策溝通的第二條規則是隻通知真正關心架構決策的人。一種有效的方法是編寫如下的電子郵件:

“嗨,Sandra,我對服務間的通信做了一個重要的決策,它會直接影響到你。請使用以下鏈接查看這個決策……”

請注意第一句話中的措辭:“對服務間的通信做了一個重要的決策。” 這裏交代了決策的上下文,而不是決策本身。第一句話的第二部分更爲重要:“它會直接影響到你。” 如果某個架構決策不會直接影響這個人,爲什麼要就這個架構決策打擾人家呢?這是一個很好的試金石,用來確定應該將架構決策直接通知給哪些利益相關者(包括開發人員)。第二句話提供了架構決策的鏈接,因而保證架構決策僅存儲在一個地方,是決策的單一系統記錄。

具備架構意義

許多架構師認爲,如果架構決策涉及任何特定技術,那它就不是一個架構決策,而是技術決策。事實上並非總是如此。如果架構師因某技術可以支持特定的架構特徵(如性能或可擴展性)而決定使用該技術,那麼這就是架構決策。

Release It!( Pragmatic Bookshelf 出版社)的作者,著名的軟件架構師 Michael Nygard, 通過創造術語 “具備架構意義”,解決了架構師應該負責哪些決策(以及什麼是架構決策)的問題。Michael 認爲,具備架構意義的決策是那些會影響結構、非功能特性、依賴項、接口或構建技術的決策。

結構是指影響架構模式或風格的決策。例如,在一組微服務之間共享數據的決策。該決策影響微服務的界限上下文,並因此影響應用程序的結構。

非功能特性是對正在開發或維護的應用程序或系統很重要的架構特徵。如果使用某個技術會影響性能,而性能是該應用程序的一個重要方面,那麼使用該技術就是一個架構決策。

依賴項是指系統內組件或服務之間的耦合點,影響整體的可擴展性、模塊化、敏捷性、可測試性、可靠性等。

接口是指訪問並編制服務和組件的方式,實現方式通常有網關、集成集線器、服務總線、API 代理。接口通常涉及合約的定義,包括合約的版本控制和棄用策略。接口會影響第三方如何使用該系統,因此具備架構意義。

構建技術指的是關於平臺、框架、工具甚至流程的決策,儘管這些決策本質上是技術性的,但可能會影響架構的某些方面。

架構決策記錄

記錄架構決策的最有效方法之一是進行架構決策記錄( ADR )。ADR 最開始由 Michael Nygard 在博客中進行宣傳,隨後在 ThoughtWorks 技術雷達中標爲 “採納”。一條 ADR 是個描述特定架構決策的短文本文件(通常爲 1~2 頁)。雖然可以使用純文本編寫 ADR,但通常用某種文本文檔格式(如 AsciiDoc 或 Markdown)來編寫。當然,也可以使用 wiki 頁面模板編寫 ADR。

也有一些管理 ADR 的工具。Nat Pryce—Growing Object-Oriented Software Guided by Tests( Addison-Wesley 出版社)的合著者— 編寫了一個 ADR 開源工具,叫作 ADR- tools。ADR-tools 提供了一組命令行接口來管理 ADR,包括編號方案、位置以及取代邏輯。來自德國的軟件工程師 Micha Kops 寫了一篇關於如何使用 ADR-tools 的文章,提供了一些使用 ADR-tools 來管理架構決策記錄的優秀示例。

基本結構

ADR 的基本結構包括五個主要部分:標題、狀態、背景、決策和後果。我們通常會在基本結構中再添加兩個內容:合規性和備註。提供模板是爲了保持一致和簡潔,在這個基本結構(如圖 19-1 所示)之上可以添加任何其他必要的內容。例如,如有必要,可以添加一個替代節,用來對其他替代解決方案進行分析

圖 19-1:基本的 ADR 結構

標題

ADR 的標題通常按順序編號,是一句簡短的描述架構決策的話。例如,在訂單服務和付款服務之間使用異步消息傳遞的決策標題可能是:“42. 在訂單服務和付款服務之間使用異步消息傳遞。” 標題應具有足夠的描述性,可以消除決策性質和背景的歧義,但同時又要簡短明瞭。

狀態

ADR 的狀態分記爲已提議、已接受或已取代。已提議意味着該決策必須由一個更高級別的決策者或某種架構管理機構(例如架構審查委員會)批准。已接受表示該決策已被批准並且可以執行。已取代表示該決策已被更改並被另一個 ADR 取代。已取代始終假定該 ADR 之前已被接受。換句話說,已提議的 ADR 永遠不會被另一個 ADR 取代,而是會繼續進行修改直到被接受。

已取代是一種保留決策歷史的非常有用的方式,能夠記錄當時做出的決策是什麼、爲什麼做出這樣的決策以及爲何做出更改。通常,當 ADR 被標爲已取代時,會標註上取代它的決策。同樣,取代另一個 ADR 的決策也應當用其取代的 ADR 進行標記。例如,假設對於先前已被接受的 ADR 42(“在訂單服務和付款服務之間使用異步消息傳遞”),由

於後來對付款服務的實現和地址進行了修改,因此現在必須使用 REST( ADR 68 )。此時,ADR 42 和 68 的狀態如下所示:

ADR 狀態的另一個重要意義在於,它迫使架構師與老闆或首席架構師進行必要的對話, 以討論他們可以自行批准架構決策的標準,或者是否必須通過更高級別架構師、架構審查委員會或其他架構管理機構的批准。

成本、跨團隊影響和安全性這三個標準能夠爲此類對話開個好頭。成本可包括軟件的購買或許可的費用、額外的硬件成本以及實施架構決策所需的工作。可以通過將預估的實施架構決策的小時數乘以公司的標準全時等效( Full-Time Equivalency,FTE)費率來估算工作成本。項目所有者或項目經理通常瞭解 FTE 的數量。如果架構決策的成本超過一定限額,則必須將它設置爲 “已提議” 狀態並經由其他人批准。如果架構決策會對其他團隊或系統產生影響,或存在任何安全隱患,則該決策不能由架構師自行批准,必須由更高級別的管理機構或首席架構師批准。

一旦建立了標準及相應的限制並達成一致(例如,“若成本超過 5000 歐元,則必須由架構審查委員會批准”),則應進行充分記錄,以便所有創建 ADR 的架構師都知道什麼時候可以自行批准架構決策,什麼時候不可以。

背景

ADR 的背景部分指明瞭決策背後的作用力。換句話說,“什麼情況迫使我做出這個決策?”ADR 的這一部分讓架構師描述具體情況或問題,並簡要闡述可能的替代方案。如果需要架構師詳細記錄每個替代方案的分析,則可以將 “其他替代方案” 部分添加到 ADR 中,而不是將該分析添加到背景當中。

背景部分還是一種記錄架構的方法。描述背景的同時,架構師也在描述架構。這是一種以清晰簡明的方式記錄架構特定部分的有效方法。繼續上一節中的例子,背景可能是:“訂單服務必須將信息傳遞給付款服務才能完成當前訂單的付款。這裏的信息傳遞可以使用 REST 或異步消息傳遞來完成。” 請注意,這條簡潔的聲明不僅說明了方案,還說明了替代方案。

決策

ADR 的決策部分包含架構決策及該決策的合理性分析。Michael Nygard 介紹了一種陳述架構決策的好方法,即使用非常正面、指揮性的表達而不是消極的表達。例如,在服務之間使用異步消息傳遞的決策可表達爲 “我們將在服務之間使用異步消息傳遞”,與“我認爲服務之間的異步消息傳遞是最好的選擇” 相比,這是一種更好的陳述決策的方式。請注意,第二種表述沒有說清楚決策是什麼,甚至沒有說明是否已有決策,僅僅只是陳述了架構師的看法。

ADR 決策部分最強大的一個地方可能是它讓架構師將重點更多地放在爲什麼( why)而不是如何( how)上。理解爲什麼要做出某個決策比了解其工作原理重要得多。大多數架構師和開發人員可以通過查看上下文圖來確定事物的工作方式,但看不出爲什麼要做出某個決策。知道做出決策的原因以及相應的依據有助於更好地理解問題的背景,並避免重構到其他解決方案,進而導致問題。

爲了說明這一點,假設幾年前原本的架構決策是使用 Google 的遠程過程調用( gRPC) 作爲兩種服務之間的通信方式。幾年後,另一位架構師在不理解爲何做出該決策的情況下,選擇取代該決策,並使用消息傳遞以便更好地分離服務。但是,這樣的重構使得延遲突然急劇增加,進而導致上游系統發生超時。如果瞭解使用 gRPC 的初衷是顯著減少延遲(以緊密耦合的服務爲代價),就能從一開始防止這樣的重構發生。

後果

後果是 ADR 另一個非常強大的部分。它記錄了架構決策的總體影響。架構師做出的每個架構決策都會產生某種影響,無論好壞。必須明確架構決策的影響迫使架構師去思考這些影響是否超過了決策帶來的收益。

後果的另一個好的用法是記錄與架構決策相關的權衡分析。這些權衡可以是基於成本的,也可以是與其他架構特徵之間的權衡。例如,假設一個決策說:使用異步(即發即棄)消息傳遞來將評論發佈到網站上。制定該決策的合理性在於,它可將評論請求的響應時間從 3100 毫秒降低到 25 毫秒,因爲用戶不再需要等待評論的發佈完成(僅需要將消息發送到隊列中)。儘管這是一個很好的理由,但考慮到與異步請求相關的錯誤處理的複雜性(“如果有人發表帶有一些不良詞彙的評論該怎麼辦?”),其他人可能會認爲這是一個餿主意,從而對決策提出質疑。對此決策提出質疑的人不知道,這個問題已經在業務利益相關者和其他架構師中間得到了討論,並且是權衡之後的決定,相比較於等同步完成之後再告訴用戶評論已發佈,在處理複雜錯誤的代價下提高響應能力更爲重要。通過利用 ADR,可以在 “後果” 部分中加入權衡分析,從而全面瞭解架構決策的背景

(以及相關權衡),從而避免出現這類情況。

合規性

合規性不是 ADR 的標準部分之一,但我們強烈建議添加。合規性迫使架構師考慮如何從合規性角度度量和治理架構決策。架構師必須決定決策的合規性檢查是手動進行,還是可以使用適應度函數來自動進行檢查。如果可以使用適應度函數進行自動化檢查,那麼架構師可以在本部分中指明如何編寫這個適應度函數,以及是否需要對代碼庫進行修改,以便對此架構決策的合規性進行度量。

例如,在圖 19-2 所示的傳統 n 層分層架構中,有這樣一個架構決策:“在業務層中,業務對象所使用的所有共享對象都將被放在共享服務層中,以隔離和保留共享功能。”

圖 19-2:架構決策示例

可以使用 Java 中的 ArchUnit 或 C #中的 NetArchTest,來自動度量和治理此架構決策。例如使用 Java 中的 ArchUnit,自動化的適應度函數測試可能如下所示:

請注意,此自動化的適應度函數需要添加新的用戶故事來創建新的 Java 註解(@Shared- Service),並將該註解添加到所有共享類當中。合規性部分還會指定相關測試是什麼、可以在哪裏找到、如何執行以及何時進行。

備註

備註是另一個不屬於標準 ADR 的部分,但我們強烈建議添加。它包含了有關該 ADR 的各種元數據,例如:

即使使用版本控制系統(例如 Git)來存儲 ADR,除了庫所支持的內容外,其他元信息也很有用,因此無論如何存儲 ADR 以及在何處存儲,我們都建議添加此部分。

ADR 的存儲

架構師創建 ADR 後,必須將其存儲在某處。無論在哪裏存儲 ADR,每個架構決策都應具有自己的文件或 wiki 頁面。一些架構師喜歡將 ADR 與源代碼一起保留在 Git 存儲庫中, 這樣還可以對 ADR 進行版本控制和跟蹤。但是,對於大型組織,出於以下幾個原因,我們不能這樣做。首先,每個需要查看架構決策的人都有可能無法訪問 Git 存儲庫。其次, 對於應用程序的 Git 代碼庫之外的上下文(例如,集成架構決策、企業架構決策或在應用程序之間共享的決策)而言,這不是好的存儲 ADR 的地方。由於這些原因,我們建議將 ADR 存儲在 wiki(使用 wiki 模板)中或共享文件服務器上的共享目錄中,可通過 wiki 或其他文檔軟件輕鬆訪問。圖 19-3 是一個此類目錄結構(或 wiki 頁面導航結構)的示例。

圖 19-3:用於存儲 ADR 的目錄結構示例

application 目錄中包含了與某些應用程序上下文相關的架構決策,該目錄可細分爲更多的目錄。common 目錄用於存放適用於所有應用程序的架構決策,例如 “所有與框架相關的類都將包含一個註解( Java 中爲 @Framework)或屬性( C #中爲 [Framework]),以將該類標識爲基礎框架代碼的一部分。application 目錄下的子目錄與特定應用或系統上下文相對應,包含特定於該應用或系統的架構決策(即本例中的 ATP 和 PSTD 應用)。integration 目錄包含與應用、系統或服務之間通信相關的 ADR。企業架構 ADR 包含在 enterprise 目錄中,表明這是影響所有系統和應用的全局架構決策。舉一個企業架構 ADR 的例子:“所有對系統數據庫的訪問,只能由擁有該數據庫的系統進行”,這條 ADR 是爲了防止多個系統共享數據庫。

當在 wiki(我們的建議)中存儲 ADR 時,前面描述的結構同樣適用,每個目錄結構都表示一個導航頁面。每個 ADR 都被表示爲導航頁面( Application、Integration 或 Enterprise)中的單個 wiki 頁面。

本節所示的目錄或頁面名稱僅作爲建議。只要能夠在團隊中保持一致,每個公司都可以選擇適合其情況的任何名稱。

以 ADR 爲文檔

記錄軟件架構一直是一個難題。儘管現在出現了一些繪製架構的標準(例如,軟件架構師 Simon Brown 的 C4 模型或 The Open Group ArchiMate 標準),但對於記錄軟件架構卻還沒有標準。這就是 ADR 可以幫忙的地方。

ADR 是一個記錄軟件架構的有效手段。ADR 的 “背景” 部分可以極好地描述系統中的某個特定部分,也就是需要做出架構決策的部分。“背景”部分還提供了描述替代方案的機會。更重要的是,“決策”部分描述了做出特定決策的原因,而這也是迄今爲止軟件架構文檔的最佳形式。通過描述架構決策的其他方面(比如捨棄可擴展性、選擇性能

的原因分析),“後果” 部分使得軟件架構文檔更加完善。

用 ADR 描述標準

很少有人喜歡標準。多數時候,標準似乎被用於控制人及人們如何做事,除此之外別無他用。用 ADR 描述標準可以改變這種不良做法。例如,ADR 的 “背景” 部分描述了強制執行特定標準的場合。ADR 的 “決策” 部分不僅可以用來說明標準是什麼,而且更重要的是說明了爲什麼需要該標準。這是能夠確定一個標準是否應該存在的絕妙方法。如果架構師無法證明一個標準的合理性,那麼制定和執行該標準可能並不好。此外,開發人員越明白爲什麼需要某個標準,他們遵循該標準的可能性就越大(因此也就不會挑戰它)。ADR 的 “後果” 部分是架構師判斷一個標準是否有效以及是否應該被制定的另一個好地方。在 “後果” 中,架構師必須考慮並記錄他們正在制定的特定標準的含義和後果。通過分析後果,架構師可能會決定放棄推行該標準。

範例

本書 7.2.1 節中存在許多架構決策。使用事件驅動的微服務、拆分出價者和拍賣者用戶界面、在視頻捕獲中採用實時傳輸協議( RTP)、使用單個 API 層、使用發佈和訂閱消息傳遞,這些只是爲這個拍賣系統做出的數十種架構決策中的幾個。無論多麼的顯而易見,系統中的每個架構決策都應記錄在案並證明其合理性。

圖 19-4 展示了 GGG 拍賣系統中的一個架構決策,即在出價捕獲、出價傳輸和出價跟蹤服務之間使用發佈和訂閱(pub/sub)消息傳遞。

圖 19-4:服務間使用發佈和訂閱模式

該架構決策的 ADR 可能如圖 19-5 所示。

圖 19-5:ADR 76. 出價服務之間採用異步發佈和訂閱消息傳遞機制

本文摘選自《軟件架構:架構模式、特徵及實踐指南》

本書是美亞廣泛好評的英文原書《Fundamentals of Software Architecture**》**的中文版,是暢銷書《卓有成效的程序員》作者 Neal Ford 的全新力作,NETSTARS CTO 陳斌等資深架構師鼎力推薦。本書全面概述了軟件架構的方方面面,涉及架構特徵、架構模式、組件識別、圖表化和展示架構、演進架構,以及其他許多主題。

**技術瑣話 **

以分佈式設計、架構、體系思想爲基礎,兼論研發相關的點點滴滴,不限於代碼、質量體系和研發管理。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/a85443kxOjnFKfRw_TARRw