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