將架構(gòu)作為語言:一個故事英文原文:Architecture as Language: A story (作者:Markus V?lter,譯者:張逸) 2008年4月16日 通常,架構(gòu)要么是在Word文檔中描述的一些軟件系統(tǒng)中無形的、概念性的方面,要么就完全是由技術(shù)驅(qū)動的(“我們使用了一個XML架構(gòu)”)。這兩種方式都很糟糕:前者很難派上用場,而后者架構(gòu)上的概念被技術(shù)宣傳所掩蓋。 什么才是好的表達?應(yīng)該是隨著架構(gòu)的發(fā)展,演化出一門語言,讓你得以從架構(gòu)的角度來描述系統(tǒng)。根據(jù)我在多個真實項目中獲得的經(jīng)驗,這種表達方式能夠形象、無歧義地描述架構(gòu)構(gòu)建模塊和具體系統(tǒng),同時又不至于深入到技術(shù)決策的細節(jié)(技術(shù)決策應(yīng)該有意識地放到另一個單獨的步驟中)。 本篇論文的第一部分通過一個真實故事演示了這一思想。第二部分則總結(jié)了這一方法的關(guān)鍵點。 一個故事 系統(tǒng)背景 我正與一位客戶在一起,他是我負責定期咨詢工作中的其中一位客戶。客戶決定構(gòu)建一個全新的航空管理系統(tǒng)。航空公司使用該系統(tǒng)跟蹤和發(fā)布不同的信息,如:飛機是否降落在指定的機場;航班是否延遲;飛機的技術(shù)狀態(tài)等等。系統(tǒng)同時還要為Internet的在線跟蹤系統(tǒng)以及在機場等地設(shè)置的信息監(jiān)控器提供數(shù)據(jù)。無論從哪個方面來看,該系統(tǒng)都屬于一個典型的分布式系統(tǒng),系統(tǒng)的各個部分分別運行在不同的機器上。它有一個中央數(shù)據(jù)中心負責處理繁重的數(shù)字運算,還有其他機器分布放置在相對廣闊的區(qū)域中。多年來,我的客戶一直在構(gòu)建類似這樣的系統(tǒng),現(xiàn)在他們計劃引入新一代的系統(tǒng)。新系統(tǒng)必須能夠支持15-20年時間的演進。單單從這一項需求就可以清楚地看出,他們需要對技術(shù)進行某種抽象,因為在這15-20年期間可能要經(jīng)歷8次技術(shù)潮流的變遷。對技術(shù)進行抽象還有另一個重要的理由,那就是系統(tǒng)的不同部分采用了不同的技術(shù)來構(gòu)建,有Java,C++,C#。采用多種技術(shù)對于大型分布式系統(tǒng)而言并非特殊的需求。通常,我們會在后端使用Java技術(shù),而在Windows前端使用.NET技術(shù)。 由于系統(tǒng)的分布式本質(zhì),不可能在同一時間更新系統(tǒng)的所有組成部分。這就產(chǎn)生了另一項需求,就是能夠一部分一部分地更新該系統(tǒng)。這就反過來要求能夠管理不同系統(tǒng)組件之間的版本沖突問題(確保組件A在組件B被升級到一個新的版本之后,仍然能夠與之協(xié)作)。 起點 在我進入項目的時候,他們已經(jīng)決定系統(tǒng)的主干應(yīng)該是一個基于消息傳遞的基礎(chǔ)架構(gòu)(對于這類系統(tǒng)而言,這是一個不錯的決策),并且他們評估了不同的消息傳遞主干在性能和吞吐量方面的表現(xiàn)。他們已經(jīng)確定了在整個系統(tǒng)中使用一個業(yè)務(wù)對象模型,對系統(tǒng)操作的數(shù)據(jù)進行描述(對于這類系統(tǒng)而言,這實際上不是一個好的決策,但它不影響這個故事的結(jié)論)。 因此,當我進入項目后,他們向我簡要地介紹了系統(tǒng)的所有細節(jié),以及他們已經(jīng)做出的架構(gòu)決策,然后詢問我這些決策是否正確。但是我很快就發(fā)現(xiàn),雖然他們了解了很多需求,也已經(jīng)在架構(gòu)的某些方面做出了細致的決策,但是卻沒有形成我所說的一致的架構(gòu)(consistent architecture):即對組成實際系統(tǒng)構(gòu)建模塊的定義,也就是定義系統(tǒng)中的各種事物。他們沒有掌握談?wù)撨@個系統(tǒng)的語言。 實際上,這只是我進入項目時的一個初步印象。當然,我認為該項目存在一個巨大的問題:如果你并不知道組成系統(tǒng)的各種事物,就很難一致地談?wù)摵兔枋鲈撓到y(tǒng),當然更無法一致地構(gòu)建該系統(tǒng)。你需要定義一門語言。 背景:這門語言是什么? 當你擁有一門語言,并能夠從架構(gòu)的角度談?wù)撓到y(tǒng)時,你就擁有了一個一致的架構(gòu)1。那么語言應(yīng)該是什么樣的呢?顯然,它首先并且至少是一套定義良好的術(shù)語。定義良好首先意味著所有的利益相關(guān)者都要認同術(shù)語的含義。如果從非正式的角度來看,術(shù)語和術(shù)語的含義可能就足以定義一門語言了。 然而——這里可能顯得有些突然——我一向鼓吹的是要用一門正式語言來描述架構(gòu)2。要定義一門正式語言,你需要的不僅僅是術(shù)語和術(shù)語的含義。你還需要一種語法來描述如何通過這些術(shù)語組成“語句”(或者模型),同時需要一種具體的句法去表示它們3。 使用一門正式的語言來描述你的架構(gòu),會帶來許多好處,隨著故事的逐漸展開,這些好處也會展露無遺。同時,在本文的末尾我會對其進行總結(jié)。 發(fā)展出一門語言以描述架構(gòu) 讓我們繼續(xù)這個故事。我的客戶與我都同意值得花上一天的時間去審閱某些技術(shù)需求,并為架構(gòu)建立一門正式語言來體現(xiàn)這些需求。實際上,我們一邊討論整個架構(gòu),一邊構(gòu)建出語法、某些約束以及一個編輯器(使用oAW的Xtext工具)。 開始 我們首先從組件的概念開始。我們對組件概念的定義是相對比較寬松的。它只是與架構(gòu)相關(guān)的構(gòu)建模塊的最小單元,封裝了應(yīng)用程序的功能。同時,我們假定組件是能夠被實例化的,以便使架構(gòu)中的組件概念對應(yīng)上OO編程中的類。因此,根據(jù)我們定義的初始語法,首先構(gòu)建的模型應(yīng)該是這樣: component DelayCalculator {} 注意,在這里我們做了兩件事情:我們首先定義了系統(tǒng)中存在組件的概念(使得組件成為我們要構(gòu)建的系統(tǒng)的構(gòu)建模塊),其次我們還(初步)決定系統(tǒng)中存在三個組件DelayCalculator,InfoScreen和AircraftModule。我們?yōu)榧軜?gòu)提出了一套構(gòu)建模塊,作為一個概念型的架構(gòu),并將這些構(gòu)建模塊的一套具體范本作為應(yīng)用程序架構(gòu)4。 接口 當然,上述關(guān)于組件的概念并無太大用處,因為組件無法交互。領(lǐng)域邏輯清晰地表明,DelayCalculator必須接收來自AircraftModules的消息,從而計算航班的延誤狀態(tài),然后將結(jié)果轉(zhuǎn)發(fā)給InfoScreens。我們知道,它們應(yīng)該以某種方式交換信息(記?。阂呀?jīng)作出了消息傳遞決策)。但是,我們決定不引入消息,而是將一組相關(guān)的消息抽象為接口5。 component DelayCalculator implements IDelayCalculator {} 我們認識到,上面的代碼看起來有幾分像是Java代碼。無需驚訝,既然我的客戶具有Java背景,那么系統(tǒng)的首選目標語言自然就是Java。因此,我們就要從他們習(xí)慣使用的語言中,抽取出廣為人知的概念衍生為我們自己的語言。然而,我們很快注意到這樣的表示方式?jīng)]有太大用處:我們無法表示組件“使用了某個特定的接口(與提供接口相對)”。了解一個組件需要哪些接口是很重要的,因為我們希望能夠了解(而且之后要用工具進行分析)組件具有的依賴關(guān)系。這對于任何一個系統(tǒng)都很重要,而對于版本管理的需求而言,則尤為重要。 因此,我們對語法稍加修改,支持如下的表達形式: component DelayCalculator { 描述系統(tǒng) 那么,我們來看看這些組件是如何被使用的。我們清晰地認識到組件需要支持實例化。很顯然,系統(tǒng)中有許多架飛機,每架飛機都運行了一個AircraftModule組件,而InfoScreens的實例數(shù)量更多。不夠明確的是我們是否需要多個DelayCalculators,但我們決定推遲對它的討論,先處理實例化的問題。 因此,我們需要能夠表示組件的實例化。 instance screen1: InfoScreen 接著,我們討論了如何把系統(tǒng)的各實例“接上線”:如何表示某個InfoScreen與某個DelayCalculator“交談”?我們必須找出某種方式來表示實例之間的關(guān)系。由于這兩個類型各自具有了“可兼容”的接口,因此,DelayCalculator可以與InfoScreen“交談”。但是暫時還難以把握這種“交談”關(guān)系。我們還注意到一個DelayCalculator實例通常會與多個InfoScreen實例“對話”。因此,我們必須以某種方式在語言中引入下標來表示實例的個數(shù)。 經(jīng)過幾番修改,我引入了端口(Port)的概念(實際上在組件技術(shù)以及UML中,這是一個眾所周知的概念,但是相對于我的客戶而言,卻是一個新名詞)。端口是在組件類型上定義的一個通信端點,當擁有端口的組件被實例化時,端口也會一同被實例化。因此,我們對組件描述語言進行重構(gòu),以支持如下的表示形式。端口通過provides和requires關(guān)鍵字進行定義,緊接著是端口的名稱和下標,一個冒號以及與端口相關(guān)聯(lián)的接口。 component DelayCalculator { 以上模型表示,任何一個DelayCalculator實例都要連接多個InfoScreens。從DelayCalculator實現(xiàn)代碼的角度來看,通過screen端口可以訪問到一組InfoScreen。而AircraftModule則只能與一個DelayCalculator“對話”,正如下標[1]所示。 新的接口標識啟發(fā)了我的客戶對IDelayCalculator進行了修改,因為他們注意到對于不同的通信對象,應(yīng)該有不同的接口(因此還應(yīng)該有不同的端口)。我們對應(yīng)用程序架構(gòu)作出了如下修改: component DelayCalculator { 注意,端口的引入改善了應(yīng)用程序架構(gòu),因為我們擁有了體現(xiàn)角色的接口(IAircraftStatus,IManagementConsole)。 現(xiàn)在,我們擁有了端口,因此我們能夠命名通信端點。這就使得我們能夠輕而易舉地描繪出系統(tǒng):互連的組件實例。注意,引入了新的結(jié)構(gòu)connect。 instance dc: DelayCalculator 保持大局觀 當然,從某種情況來看,為了不至于混淆所有的組件、實例和連接器(connectors),我們無疑需要引入某種命名空間的概念。自然,我們也可以將這些內(nèi)容分別放到不同的文件中(工具支持保證了“轉(zhuǎn)到定義”和“查找引用”仍然正常)。 namespace com.mycompany { 當然,將組件和接口的定義(本質(zhì)上是類型的定義)與系統(tǒng)的定義(連接的實例)分開,是一個很好的想法,因次,我們?nèi)缦露x了一個系統(tǒng): namespace com.mycompany.test { 在一個真實的系統(tǒng)中,DelayCalculator必須能夠在運行時動態(tài)地發(fā)現(xiàn)所有可用的InfoScreens。手動地描述這些連接是沒有什么意義的。因此,我們需要繼續(xù)前進。我們定義了一個查詢,它可以采用naming/trader/lookup/registry的基礎(chǔ)架構(gòu)在運行時執(zhí)行。每隔60秒,查詢會被執(zhí)行一次,查找任何上線的InfoScreens。 namespace com.mycompany.production { 可以使用相似的辦法實現(xiàn)負載均衡或者容錯能力。一個靜態(tài)的連接器能夠指向一個主要實例以及備份實例。或者,在當前使用的組件實例變?yōu)椴豢捎脮r,可以重新執(zhí)行一個動態(tài)查詢。 為了支持實例的注冊,我們在它們的定義中添加了額外的語法。一個registered的實例會在注冊記錄中使用自己的名稱(通過命名空間識別)以及所有提供的接口,自動注冊其本身。還可以指定額外的參數(shù),如下的例子就為DelayCalculator注冊了一個主要的實例和一個備份的實例。 namespace com.mycompany.datacenter { 第二部分,接口 至今我們?nèi)匀粵]有真正定義一個接口究竟是什么。我們知道,我們更愿意基于一個消息傳遞的基礎(chǔ)架構(gòu)來構(gòu)建系統(tǒng),因此,接口顯然必須定義為消息的集合。于是就有了我們最初的想法:一組消息的集合,其中每條消息都有名稱,以及一組類型化的參數(shù)。 interface IInfoScreen { 當然,同時還需要具備定義數(shù)據(jù)結(jié)構(gòu)的能力。因此,我們添加了這樣的內(nèi)容: typedef long ID 在對接口進行了一段時間的討論之后,我們現(xiàn)在注意到簡單地將接口定義為一組消息還遠遠不夠。我們希望做到的最小要求是能夠定義消息的方向:它是流入端口還是流出端口?或者更一般地說,系統(tǒng)中存在哪些消息交互模式?我們識別出了好幾個,這里是oneway和request-reply的范例: interface IAircraftStatus { 真的是消息嗎? 我們對各種消息交互模式進行了長時間的討論。顯然,消息的其中一種核心用例就是將各種資源的狀態(tài)更新發(fā)送到各個對其關(guān)注的部分。例如,如果航班因為飛機的一個技術(shù)問題而延誤,則該信息就會被發(fā)送到系統(tǒng)的所有InfoScreens中。我們?yōu)橐粋€確切狀態(tài)項的“廣播式”完整更新、增量更新、無效更新等方式建立了必需的幾種消息原型。 然而,現(xiàn)實卻給了我們沉重的打擊:我們一直在一種錯誤的抽象中工作!雖然消息傳遞對于這些事項而言是一種適合的傳輸抽象,但我們真正談?wù)摰钠鋵崙?yīng)該是復(fù)制的數(shù)據(jù)結(jié)構(gòu)(replicated data structures)?;旧希械倪@些結(jié)構(gòu)都采用同樣的方式工作:
當然,一旦我們了解到除了消息之外,系統(tǒng)還包括額外的核心的抽象,我們就應(yīng)該將它添加到我們的架構(gòu)語言中,并能夠像下面所示的方式進行編寫。我們定義了數(shù)據(jù)結(jié)構(gòu)和復(fù)制項。然后,組件能夠發(fā)布(publish)或者使用(consume)這些復(fù)制的數(shù)據(jù)結(jié)構(gòu)。 struct FlightInfo { 毫無疑問,上面的描述比基于消息的描述更準確。系統(tǒng)能夠自動地衍生出完整更新、增量更新和無效更新等需要的各種消息。這一描述同樣清晰地反映了實際的架構(gòu)意圖:比起那種僅僅表達了我們希望如何去做(發(fā)送狀態(tài)更新消息)的較低級的描述,新的描述方式更好的表達了我們希望做什么(復(fù)制狀態(tài))。 當然,我們還不能停下前進的腳步?,F(xiàn)在,我們擁有了作為“頭等公民”的狀態(tài)復(fù)制,就能夠為它的技術(shù)規(guī)范添加更多的信息: component DelayCalculator { 上例的意思是,只要底層的數(shù)據(jù)結(jié)構(gòu)的內(nèi)容發(fā)生改變,發(fā)布者就會發(fā)布復(fù)制的數(shù)據(jù)。然而,InfoScreen只需要每隔60秒進行一次更新(當它剛啟動的時候,會對數(shù)據(jù)作一次完整的加載)。根據(jù)這一信息,我們能夠產(chǎn)生出所有需要的消息,同時為參與者生成一個更新時間表。 更多內(nèi)容? 在余下的討論中,我們識別了架構(gòu)的其他幾個方面,并為它們添加了語言抽象:
結(jié)論 采用這種方法,我們能夠快速地把握系統(tǒng)的整體架構(gòu)。我們還因此能夠區(qū)分開“希望系統(tǒng)做什么”和“系統(tǒng)如何實現(xiàn)它”:這樣一來,技術(shù)層面的討論僅僅屬于為此處給出的概念性描述提供實現(xiàn)細節(jié)(當然是非常重要的實現(xiàn)細節(jié))。我們明白無誤地理解了不同術(shù)語所代表的含義,并給出了明確的定義。組件這一模糊的概念在這個系統(tǒng)中具有了正式的、明確界定的含義。 當然,它并沒有到此為止。下一步要討論的是如何為組件的實現(xiàn)進行編碼,以及討論系統(tǒng)的哪一部份可以被自動生成。更多內(nèi)容參見下一節(jié)。 扼要總結(jié)&優(yōu)勢 我們做了什么 這種方法包括為項目或系統(tǒng)的概念性架構(gòu)定義一門正式的語言。隨著你對架構(gòu)的深入理解,逐步發(fā)展了這門語言。因此,語言總是與你對架構(gòu)完整而又明晰的理解相對應(yīng)。隨著我們對語言的增強,我們就能夠使用該語言對應(yīng)用程序架構(gòu)進行描述。 背景:DSL 我們前面前面建立起來的語言是一種DSL——領(lǐng)域特定語言。以下是我對DSLs定義: DSL是一種目的明確的、可處理的語言,當我們在一個特定領(lǐng)域內(nèi)構(gòu)建系統(tǒng)時,可以用它來描述一個特定的關(guān)注點。它所使用的抽象與標識符號是為那些指定特定關(guān)注點的利益相關(guān)人定制的。 DSLs可以用來指定軟件系統(tǒng)的各個方面。其中一大看點是使用DSL可以描述業(yè)務(wù)功能(例如,在保險系統(tǒng)中的計算規(guī)則)。DSL尤其在描述業(yè)務(wù)功能時倍顯其價值所在,同樣,也完全值得用DSL描述軟件架構(gòu):正如我們在這里所做的那樣。 因此,我們先前構(gòu)建的架構(gòu)語言——以及我在本篇論文中倡導(dǎo)的方法——其意義在于使用DSL技術(shù)去定義一種描述特定架構(gòu)的DSL。 優(yōu)勢 參與的每個人都能清晰地理解用于描述系統(tǒng)的概念。提供清晰明確的詞匯來描述應(yīng)用程序。創(chuàng)建的模型可以被分析,并作為代碼生成(如下所示)的基礎(chǔ)被使用。架構(gòu)總是與實現(xiàn)細節(jié)無關(guān),或者換句話說:概念型架構(gòu)與技術(shù)決策是解耦的,從而使得它們更加便于各自的演化發(fā)展。我們同樣能夠根據(jù)概念型架構(gòu)定義一個清晰的編程模型(如何使用之前定義的所有架構(gòu)特征對組件進行建模和編碼)。最后,現(xiàn)在架構(gòu)師就可以通過構(gòu)建(或者幫助構(gòu)建)團隊其余成員實際能夠使用的工件,直接為項目作出貢獻。 為何使用文本形式? ……或者為什么不使用圖形標識?文本型的DSLs有幾大優(yōu)勢。首先是更加容易建立語言以及一個好的編輯器。其次,文本型的工件比圖形化的模型庫更加容易集成到現(xiàn)有的開發(fā)工具(CVS/SVN diff/merge)中。第三,文本型的DSLs通常更容易被開發(fā)者接受,因為“真正的開發(fā)人員不畫圖”。 如果對于系統(tǒng)的某些方面,圖形標識有助于看清楚架構(gòu)元素之間的關(guān)系,你可以使用類似于Graphviz或者Prefuse之類的工具。既然模型以一種清晰而又干凈的形式包含了相關(guān)的數(shù)據(jù),我們就可以輕易的將模型數(shù)據(jù)導(dǎo)出成GraphViz或者Prefuse工具能夠閱讀的形式。 工具 要使得前面介紹的方法具有可行性,你需要用工具來支持DSLs的高效定義。我們使用了openArchitectureWare的Xtext。Xtext能夠為你完成如下事情:
經(jīng)過一點實踐就可以掌握Xtext,它真正讓你能夠按照自己對架構(gòu)細節(jié)的理解和架構(gòu)決策來設(shè)計語言。自行訂制代碼完成功能可能需要比較長的時間,但是你可以在對語言的摸索告一段落的時候再做這件事。 驗證模型 如果我們要正式而且準確的描述一個架構(gòu),除了語法,我們還需要實施驗證規(guī)則,對模型進行約束。簡單的例子比如典型的名稱唯一性約束、類型檢查或非空檢查。要表示這些(相對的)局部約束,可以直接使用OCL或者類似于OCL的語言。 但是,我們還需要驗證更加復(fù)雜,而且不那么局部的約束。例如,在前面介紹的故事里,約束會檢查組件和接口的新版本是否與它們的舊版本實際上是兼容的,因此可以用在相同的上下文中。要能夠?qū)崿F(xiàn)這樣重要的約束,有兩個前置條件是非常必要的:
生成代碼 從本篇論文中可以逐漸清晰地了解到,發(fā)展架構(gòu)的DSL(以及使用DSL)的關(guān)鍵優(yōu)勢在于:清晰無誤地理解概念,并正式地定義它們。它有助于你理解你的系統(tǒng),以及去除那些不必要的技術(shù)干擾。 當然,現(xiàn)在我們已經(jīng)擁有了一個概念型架構(gòu)的正式模型,以及我們正在構(gòu)建的系統(tǒng)的正式描述(使用語言定義的語句(或模型)),我們將利用它獲得更多的好處:
當然,為多種目標語言生成API(支持用多種語言來實現(xiàn)組件)以及/或者為多個目標平臺(支持在不同中間件平臺執(zhí)行相同的組件)生成膠合代碼都是完全可行的。這就很好地支持了可能的多平臺的需求,同時也提供了一種方法使得基礎(chǔ)架構(gòu)能夠隨著時間的推移擴展規(guī)模,或者進行演化。 另一個值得注意的是,你通常應(yīng)該分為幾個階段來生成代碼:第一個階段是使用類型定義(組件、數(shù)據(jù)結(jié)構(gòu)、接口)去生成API代碼,這樣你才能對實現(xiàn)進行編碼。第二個階段是生成膠合代碼以及系統(tǒng)配置代碼。最后,將類型定義從模型中的系統(tǒng)定義分離出來,這是一種明智的做法:因為在整個過程中,它們會在不同的時刻被使用,而且通常會被不同的人創(chuàng)建、修改與處理。 總的說來,生成的代碼支持有效的、獨立于技術(shù)的實現(xiàn),能夠隱藏大多數(shù)潛在的技術(shù)復(fù)雜性,從而使得開發(fā)更加高效。 如何比較它與ADLs和UML 用正式的語言描述架構(gòu)并非一個新的想法。各個社區(qū)都推薦使用架構(gòu)描述語言(ADLs)或者統(tǒng)一建模語言(UML)描述架構(gòu)。有的甚至可以(試圖)從結(jié)果模型中生成代碼。但是,所有這些方法都主張使用現(xiàn)有的通用語言來記錄架構(gòu)(雖然有一些語言能夠被定制化,包括UML)。 然而(你可能從上述的故事中看出端倪)這完全忽略了重點!我并沒有看到這種將架構(gòu)描述硬塞到預(yù)定義/標準化語言提供的(通常是非常有限的)結(jié)構(gòu)中,會帶來多少好處。在本篇論文所闡釋的方法中,其中一個核心活動是實際構(gòu)建你自己的語言去捕捉系統(tǒng)的概念型架構(gòu)的過程。讓你的架構(gòu)適配于ADL或者UML提供的不多的概念,對架構(gòu)設(shè)計并無多大幫助。 關(guān)于UML Profile:是的,你可以把前面介紹的方法用在UML上面,建立一個UML Profile,而不是文本型語言。我在好多個項目中采用了這個方法,得到的結(jié)論是它在大多數(shù)環(huán)境下工作得并不好。原因如下:
為什么不直接使用編程語言 架構(gòu)的抽象,例如消息或組件在現(xiàn)今的第3代編程語言中,并非“頭等公民”。當然,你可以使用類來表示它們。使用注解(也稱為特性),你甚至可以關(guān)聯(lián)元數(shù)據(jù)與類和類的其他內(nèi)容(操作、字段)。因此,你總是可以使用第三代語言來表示這些內(nèi)容的。但是,這種方法存在問題:
我對組件的觀點 對于什么是組件存在著許多種(或正式或非正式)定義。從軟件系統(tǒng)的構(gòu)建模塊,到有顯式定義的上下文依賴關(guān)系的物件,到包含了業(yè)務(wù)邏輯并運行在容器中的物體,都可以稱之為組件。 我的理解為(注意,我并不是說我提出了一個真正的定義)組件是最小的架構(gòu)構(gòu)建模塊。在定義系統(tǒng)的架構(gòu)時,無需關(guān)注組件的內(nèi)部。組件必須以聲明方式指定它們與架構(gòu)相關(guān)的屬性(即以元數(shù)據(jù)或模型的方式指定)。因此,組件可以通過工具進行分析和組合。通常,它們都運行在容器中,而容器則體現(xiàn)為框架,處理著元數(shù)據(jù)中與運行時相關(guān)的部分。容器在哪個層次上提供技術(shù)服務(wù)(日志、監(jiān)控、故障轉(zhuǎn)移等),那就是組件的邊界。 對于組件實際包含的元數(shù)據(jù)(以及元數(shù)據(jù)描述了什么屬性),我并無任何具體的要求。我認為,組件的具體概念必須針對每個(系統(tǒng)/平臺/產(chǎn)品類型)架構(gòu)來定義。而這實際上也是我們在前面介紹的通過語言方式所要做到的。 組件實現(xiàn) 默認情況下,組件的實現(xiàn)都是手動完成的。實現(xiàn)代碼可以針對之前介紹的生成的API代碼來編寫。要想在組件的骨架中增加手工編寫的代碼,開發(fā)者可以直接將代碼添加到生成的類中,或者——更好的方式是——使用組合例如繼承或者局部類(partial classes)。 還有其他替代方法可以實現(xiàn)組件,它們不使用第3代編程語言,而是針對需要描述的行為采用專門的形式化手段。
同樣可以使用動作語義語言(ASLs,Action Semantics Languages)作為替代的方法。但是,需要指明的重要一點是,該語言沒有提供領(lǐng)域特定抽象,而是采用與通用建模語言例如UML相同的方式。然而,即使你使用了更特定的標記法,仍然免不了需要泛化地指定小個片段的行為。一個很好的范例就是在狀態(tài)機中的動作。 為了有效地結(jié)合用組件概念來定義行為的各種方法,可以使用元層次的子類化手段去定義各種組件,讓每個組件都有自己的一組表示法去定義行為。下圖說明了這一原理。
既然從技術(shù)上講,組件實現(xiàn)就是與行為有關(guān),因此通常來說使用封裝在組件內(nèi)部的解釋器是有效的。 最后,值得一提的是,我們要認識到本節(jié)討論的內(nèi)容只涵蓋應(yīng)用程序特定的行為,而不是指所有的實現(xiàn)代碼。大量的實現(xiàn)代碼都與應(yīng)用程序的技術(shù)基礎(chǔ)架構(gòu)息息相關(guān)——遠程處理、持久化、工作流等——而它們都可以從架構(gòu)模型衍生出來。 模式的角色 在今天的軟件工程實踐中,模式是相當重要的一部分。對于重復(fù)出現(xiàn)的問題,模式是一種經(jīng)過驗證的有效解決方案,模式的適用性、利弊和后果都是經(jīng)過檢驗的。那么,模式在前面描述的方法中,又扮演了怎樣的角色呢?
在談到DSLs、代碼生成以及模式時,需要提及的是你不能完全地自動化模式!一個模式并不是只包含UML圖表的解決方案。在模式的定義中有很大的篇幅用來解釋模式受到哪些力量的影響,何時可以應(yīng)用模式何時不應(yīng)該應(yīng)用模式,以及使用模式會帶來何種結(jié)果。模式的文檔中通常還會記錄下模式的多種變體,每種變體都各有不同的優(yōu)勢與缺點。如果環(huán)境有特殊之處,開發(fā)人員在實現(xiàn)模式的時候必須將之考慮在內(nèi),對它們進行評估,相應(yīng)作出決策。 哪些內(nèi)容需要記入文檔? 我一直在鼓吹上述方法可以作為正式描述系統(tǒng)概念與應(yīng)用程序架構(gòu)的一種方法。因此,就意味著它起到了某種文檔的作用,對嗎? 是的,但這并不意味著你不需要將其他任何內(nèi)容納入文檔。下列內(nèi)容仍然需要編檔:
架構(gòu)還有許多方面可能值得我們?nèi)ゾ帣n,但上述兩點是其中最重要的。 進一步閱讀 如果你喜歡本篇論文所闡釋的方法,你可能需要閱讀我整理的架構(gòu)模式。它們延續(xù)了本文的模式話題,并為本文介紹的內(nèi)容提供了理論基礎(chǔ)。這篇論文雖然有點舊,但是本質(zhì)上論述的話題仍然是相同的。可以通過如下地址獲得http://www./data/pub/ArchitecturePatterns.pdf。 另外一個值得了解的內(nèi)容是領(lǐng)域特定語言和模型驅(qū)動軟件開發(fā)的完整知識。我撰寫了許多關(guān)于這方面的文章,最主要的,我還是《模型驅(qū)動軟件開發(fā)》一書的合作者——從中你可以了解到關(guān)于技術(shù)、工程學(xué)、管理學(xué)的內(nèi)容。更多信息請訪問:http://www./publications/books-mdsd-en.html。 當然,通常情況下你還需要了解關(guān)于Eclipse建模、openArchitectureWare和Xtext的更多細節(jié)內(nèi)容。在eclipse.org/gmt/oaw上可以訪問到許多相關(guān)的信息,包括官方的oAW文檔以及大量的入門視頻。 致謝 我要感謝Iris Groher、Alex Chatziparaskewas、Axel Uhl、Michael Kircher、Tom Quas和Stefan Tilkov,感謝他們對本篇論文的前一個版本提出的精彩評論。 關(guān)于作者 Markus V?lter是一名獨立的咨詢師,軟件技術(shù)和軟件工程的指導(dǎo)教練。他專注于軟件架構(gòu)、模型驅(qū)動軟件開發(fā)與領(lǐng)域特定語言以及產(chǎn)品線工程(product line engineering)。Markus就中間件和模型驅(qū)動軟件開發(fā)領(lǐng)域(合作)撰寫了多篇雜志文章、書籍以及提出了多種模式。他經(jīng)常在各種世界大會上發(fā)表演講。你可以給他發(fā)送郵件voelter@acm.org,或者訪問www.與他取得聯(lián)系。
|
|
|