簡介: 這一期的 演化架構和緊急設計 將會解決演化架構相關的各種主題,包括設計和架構之間的重 要區別(以及如何區分兩者),您在創建企業級架構時遇到的某些問題,以及面向服務的架構中靜態類型 和動態類型的區別。
在 本系列的第一期 中,我推薦了軟件世界中的一些架構定義。無論如何,如果您已經閱讀過本系列 ,您會注意到我花費了大部分時間在設計上。我之所以這麼做是基於以下幾個原因:首先,在當前緊急設 計尚未被廣泛關注時,軟件世界裡存在很多架構定義(良莠不齊);其次,在設計方面很多問題都有具體 的、不受環境限制的解決方案。架構往往還涉及到很多組織內的物理和邏輯基礎設施,使其難以獨立談起 。
關於本系列
本 系列 旨在從全新的視角來介紹經常討論但是又難以理解的軟件架構和設計概念。通過具體示例, Neal Ford 將幫助您在演化架構 和緊急設計 的靈活實踐中打下堅實的基礎。通過將重要的架構和設計決 定推遲到最後責任時刻,您可以防止不必要的復雜度降低軟件項目的質量。
這一期填補了敏捷構架材料缺失的空白。在此我討論的是如何分辨架構和設計,涵蓋了一些廣泛的架 構考慮,然後通過討論版本控制端點,淺談敏捷的面向服務架構(SOA)。
分辨架構和設計
Martin Fowler 對架構的定義(來自和他的對話中)是我認為最好的:
架構就是完成之後很難更改的東西。所以這種東西應該盡可能越少越好。
您可以想象一下架構和設計之間的交互,如圖 1 中所示的關系:
圖 1. 架構和設計的關系
一個軟件系統的架構形成是所有其他部分存在的基礎,如 圖 1 中的灰盒所示。設計部分存在於架構 之上,如紅盒所示。處於更基礎的地位,架構部分難以移動和替換是因為您不得不移動所有以架構為基礎 的部分來適應改變。這一定義使識別設計和架構更為簡單。例如,您所使用的 Web 框架就是一個架構, 因為它難以替換。盡管,在那個 Web 框架中您使用不同的設計模式來表述特定的目標,這就表示大部分 的正式設計模式是設計,而不是架構的一部分。
Fowler 所定義的架構的推論是:您應該靈活地構造架構部分,以便能夠更輕松地替換它們(如果真的 需要的話)。但是如何才能確保這點呢?這裡有個例子。
許多框架都會試圖誘導您使用其自身的類,而不是 JDK 或者一個開放標准機構(例如 OASIS)提供的 更普遍的類。這就是耦合的 “毒販模式”:如果您服從這些誘導,您就只能永遠受制於框架。這些框架 采取的普遍方法就是,如果您使用了它們的類,某方面就會變得異常簡單。這方面的完美例子就來自於 Apache Struts Web 框架。
在您的應用程序中包含業務規則和其他非基礎設施代碼的類是域類:它們包含著您的問題領域相關的 有趣信息。Sturts 中的一個好助手類就是 ActionForm 類。如果您從 ActionForm 繼承了您的域對象, 您的應用程序就會變得更方便。您可以從參數完成自動表格填充、自動驗證(Web 和服務器層),以及其 他便利。您所要做的就只是把 Struts ActionForm 類作為子集,如圖 2 所示:
圖 2. 使用 Struts ActionForm 類
在 圖 2 中,標簽為 Model 的盒子包含了您的域對象。它擴展了 Struts 的 ActionForm,使得這一 結構此後難以改變。如果以後您決定 ScheduleItem 也需要在一個 Swing 應用程序中運行,那就很難辦 了。您只剩下兩個難以接受的解決方案:將所有的 Struts 拖拽到 Swing 應用程序中(且不使用它)或 者擺脫對 Struts 的依賴。
較好的替代方案就是采用組合而不是繼承,如圖 3 所示:
圖 3. 通過組合來對您的域類解耦合
在此版本中,域類(黃色部分)包含了一個定義日程項目語義的界面。原始的 ScheduleItem 將實現 這個界面,它還可以由 ScheduleItemForm 來實現,使得這兩個類的語義總是保持一致。反過來, ScheduleItemForm 擁有 ScheduleItem 域對象的一個實例,ScheduleItemForm 的所有讀值器和寫值器傳 遞到封裝的 ScheduleItem 的底層讀值器和寫值器。這就允許您利用 Struts 的良好特性,同時擺脫該框 架的束縛。
經驗法則是:可以使框架對您有所了解,而您不可以對框架有所了解。只要您可以維持那種關系,您 就能避免把自己的代碼耦合到基礎設施中去,這使您能夠更輕易地改變架構和設計。有時可能要多花點功 夫來完成這個任務,但是您可以擁有更好的靈活性。Struts 並不是唯一向您提供這種誘惑的框架。幾乎 所有的框架都包含將您限制在框架中的幫助工具。如果您在域類中導入來自某個框架或者廠商的數據包, 那您以後就有得頭疼了。
關於架構的考慮
除了架構的定義,典型的企業設置中還出現了各種廣泛的問題。我將在這裡介紹針對其中一些問題的 敏捷架構解決方法。
架構的政治
當您被提升到架構師職位時,公司政治將是您所要遇到的眾多難題之一。因為架構師 基本上是公司中 最高的技術職位,您會成為 IT 部門內發生的所有決策的發言人(和辯護人),無論好壞。事實上,您還 常常要因為失敗受到責備,卻不會因為成功而贏得信任。一些新上任的架構師試圖對這些置之不理(當您 在技術職位時這也許非常有效),但是在您的新職位這明顯行不通。
請您記住在許多軟件項目中,溝通比技術更為重要。如果您曾經在某個軟件項目上失敗過,那麼請您 思考一下失敗的原因:是出於某個技術 原因,還是某些溝通 問題?大部分時間,失敗是因為溝通而不是 技術。技術問題有其解決方案。(有時它們很難解決,但總歸有解決方案。)但社會問題就更加復雜和棘 手了。Peopleware這本書中有這樣一句名言:
總是存在人的問題。
即使是您認為應該按部就班,直截了當的技術決策,也會有政治參雜其中,特別是您處於決定是否批 准購買某企業工具的職位。(從樂觀的角度看,您可能有機會由某個工具廠家掏腰包打次異國情調的高爾 夫。)請記得,作為一名架構師,您不僅需要做出重要的決策,您還必須為這些決策辯護。有時和您交談 的人有他們自己的議事日程,這些內容或許在邏輯上行不通,但是在企業政治的考驗面前卻行得通。不要 氣餒,您要記清楚最初之所以作出這個決策的原因。
構建與購買
大公司中常出現的普遍問題之一就是決定是構建還是購買:針對現在的需求,我們是應該購買 COTS( Commercial Off-the-Shelf Software)還是自己構建?要做出此決策的動機是可以理解的 — 如果公司 可以找到一些完全符合自身需要的現成軟件,這樣就節約了時間和金錢。不幸的是,許多軟件廠商理解這 一需求,所以編寫可以定制的打包軟件,如果軟件不能完全符合客戶的需要的話。他們意在盡力構建最通 用的軟件,因為這樣能適用更多的生態系統。但是越是通用,就越需要定制。所以有時即使很多顧問在, 也需要花費很多年才能完成所有的定制代碼。
是否應該購買 COTS 的問題實際上歸結為另一個問題:業務流程是由軟件在戰略上 還是經費上 支持 ?如果業務流程僅僅是經費問題,購買 COTS 就合情合理。這類軟件例子包括人力資源、財務、以及其他 普通的業務流程。戰略 軟件在您的業務領域給您競爭優勢,這個競爭優勢不能輕易放棄。
避免陷阱
請記住:並不是所有的業務流程都是可定制的,它們根據業務不同而千差萬別。不要輕信那些聲稱已 經編寫了您要的業務流程的廠商。如果他們真的擁有這樣的流程,他們肯定也在把這些流程賣給您的競爭 對手。
圖 4 所示的流程圖用於幫助您決定是構建還是購買:
圖 4. 決策是構建還是購買的流程圖
在這個流程圖中,您要做出的第一個決策就是戰略和經費的重要區別。如果需求是戰略性的,您往往 需要自己構建解決方案。如果不這麼做,您就會將自己置於一個和對手公平競爭的環境中,而不是構建完 全符合您現在和將來需求的軟件。打包軟件吹噓其可定制性,但還是有對定制程度的限制。如果您自己編 寫,會花費較長的時間,但是您有了一個平台,在這個平台上您可以構建將您和對手區分開的軟件。
流程圖中的第二個決策就是詢問數據包軟件是否能立刻起作用。在購買數據包軟件時常見的一個陷阱 就是錯誤估計其適應您的業務流程所需的准確時間;大部分公司都把這個時間錯估了一個數量級。您所需 的定制越多,所耗費的時間就越長。更糟糕的是,一些公司還允許改變他們的業務流程來適應軟件。這是 一個錯誤,因為無論好壞,您的業務流程都應和對手的有所區別。
這個決策樹中的第三步就是詢問數據包是否可擴展,這和定制性 剛好相反。可擴展的系統由經過良好 定義的方法來擴展功能,而無需一切事先就緒。這些擴展點包括經過良好定義的 APIs、SOAP 調用等等。 定制意味著您要通過 “欺騙” 來讓數據包完成您的工作。例如,如果您試圖打開一個 WAR 文件,那麼 您可以用一個不同的圖像(必須用 index.gif 來命名)來替換用 index.gif 命名的文件,您是進行定制 而不是擴展。最終檢驗標准是您的更改是否能夠通過升級。如果是,您就擴展了數據包;如果不是,您就 定制了數據包。定制不鼓勵您不斷升級數據包,因為您會意識到對新版本做出相同的改變需要付出多少努 力。那麼,趨勢就是不進行更新,落後於最新版四、五個版本,這將使您面臨失去對現在正在使用的老版 本的支持的危險。
是經費問題還是戰略問題因公司而異。例如,我曾為一家財務服務公司做過顧問,它的招聘過程被認 為是其關鍵戰略優勢之一。他們雇傭最好、最聰明的人,花費大量的時間和精力來尋找適合的人。他們曾 就購買 COTS 人力資源系統咨詢過我的意見,我建議他們不要那樣做:為什麼要讓自己置身於一個和對手 公平競爭的環境呢?最後,他們采納了我的建議,編寫自己的 HR 系統。編寫花費了較長的時間,但一旦 完成,他們就有了一個平台,能夠完成對其對手來說更勞動密集型的任務。招聘對許多組織來說是簡單的 經費問題,但對這家公司來說卻是戰略問題。
架構中的類型控制
SOA 計劃中經常出現的一個更技術化(更不面向流程)的主題往往和分布式系統中的類型控制和版本 控制有關。這就是這類項目中常見的陷阱之一。它之所以常見,不僅因為人們很容易遵循工具廠商鋪好的 路,還因為問題需要一段時間才能凸顯出來 — 最嚴重的問題產生於您不了解在項目早期應該知道的東西 。
關於能否用動態類型語言構建 “企業” 系統的爭論已經有了定論,這個結論現在也不能給予什麼啟 示。然而,這一爭論意味著就端點的類型控制而言,對分布式系統有了重要的考慮。所謂端點,指的是兩 個完全不同的系統之間的通信門戶。兩個相互競爭的類型控制樣式是 SOAP 和 Representational State Transfer (REST),前者通常采用諸如 Web Services Description Language (WSDL)這樣的標准來創建 一個強類型,而後者適用於類型更寬松的、以文檔為中心的方法。SOAP 與 REST 的詳細優缺點不在本文 的討論范圍之內;在此我主要想說的是端點層面上寬松類型的好處,這些好處可以使用任一樣式實現。
更動態的類型控制在端點處是很重要的,因為這些端點會在以不同速度演變的系統之間形成一個已發 布的集成 API。您想在那些系統之間避免嚴格耦合的特定簽名(類型和參數名),因為那樣會使通信的雙 方都很脆弱、容易崩潰,削弱了您分別對兩個應用程序進行版本升級的能力。
這裡有個例子。在傳統的 SOAP 式集成中,使用的協議類型是 Remote Procedure Call (RPC),並用 WSDL 來定義兩個應用程序間通話的詳細信息,如圖 5 所示:
圖 5. 在應用程序間使用 RPC 式調用
RPC 式集成使用 WSDL 來進行一個 “常規” 方法調用,並將其抽象出來發送到 SOAP。這樣,每個類 都映射到 WSDL 中的一個類型,包括其所有參數的類型。這種方法將通信雙方強烈耦合到一起,因為它們 都依賴 WSDL 來定義發送的內容和預期接收的內容。問題源於這種嚴格的定義。如果您需要修改其中一個 應用程序來采用不同的參數或者改變現有的類型,且不能同時更改這兩個應用程序,那又該怎麼辦呢?該 如何對端點進行版本控制?有幾個方法是可行的,但所有這些方法都有嚴重的妥協之處。例如,您可以用 新的 WSDL 定義創建另外一個端點。如果原始端點命名為 addOrder,那麼您可以創建另一個端點,命名 為 addOrder2。您會看到這種方法前景不妙。不久,您就會有數十個稍有不同、到處包含重復代碼的端點 來處理一次性情況,因為一旦發布,就很難預測人們會怎麼應用這些集成點。您也可以使用 Universal Description, Discovery, and Integration (UDDI)(或者僅僅是哈希圖)這樣的工具來欺騙端點,但那 並不會有很好的效果。根本問題就是端點間的嚴格耦合,那會阻止其按自然、獨立的速度發展演變。
一種替代方法就是把集成端點當做寬松類型對待,如圖 6 所示:
圖 6. 在集成端點采用寬松類型控制
通過將有趣的端點信息傳送到一個文檔內,您可以在通信雙方任意一方主要升級和次要升級過程中保 持端點定義不變。您可以靈活選擇,而不是依賴 WSDL 來嚴格定義預期的內容。現在,端點總是接收一個 封裝該端點所需類型的文檔。
要解決端點的版本控制問題,端點要做的第一步就是把文檔解開,確定已經傳輸的內容,並用預期的 內容與之協調。我通常聯合使用 Factory 和 Strategy 設計模式來確定是否正在獲得預期的內容,如圖 7 所示:
圖 7. 在端點內解開內容來確定類型
端點的首要工作就是查看文檔的清單,確定它包含的內容。然後,它用一個庫來實例化適當的策略, 將那些信息從文檔中抽出。一旦所有部分都通過驗證(必要時可用 WSDL),反序列化的對象就被傳遞, 用於業務處理。
這種方法有幾個好處。首先,擁有一個帶有兩個正交作業的機制是個壞主意,然而那正是傳統 RPC 所 假設的:端點既要負責提供已發布的集成 API,又要負責驗證類型。因為其有兩個行為,所以您可能會弄 混代碼,使其更難理解和維護。其次,現在這個端點可以有多個用戶,每個用戶使用稍有差異的版本。只 要您有一個策略,您就能夠用相同的端點支持任何版本(包括那些更新緩慢的應用程序的老版本)。這允 許您根據需要進行改變,不用擔心這會迫使企業內應用程序的其他部分和您的改變保持一致 —— 它們可 以根據自己的進度改變並使用新的文檔版本。
目前沒有任何工具或者框架允許您輕松地實現這種方法,但是一些額外的前期工作提供了之前提到過 的好處。您可以使用 SOAP 或 REST 來實現這個樣式(不過在 REST 中會更容易,因為它本身就是以文檔 為中心的)。通過創建一個寬松類型的生態系統,您可以使不同的開發小組按自己的節奏開展工作,從而 使整個企業的應用程序使用以最小的摩擦前進。這就是演化架構的精髓:奠定一個基礎來支持盡快實施無 摩擦的、不損害功能的變革。
結束語
架構是個龐大且復雜的軟件主題;在這部分中,我試圖涉及許多不同的方面,從政治到 SOA 中的端點 版本控制的實現細節。在以後的部分中,我將不斷充實這些關於一般架構和新架構方法的想法,幫助您構 建一個可發展的 SOA,以免向軟件商支付數百萬美元的高額費用。