
- 類型:濾鏡插件大�。�7.6M語言:中文 評(píng)分:6.6
- 標(biāo)簽:
我們何時(shí)可以認(rèn)為軟件產(chǎn)品被真正地完成了呢?通常情況下,當(dāng)我們不再為其提供后續(xù)支持,或者該產(chǎn)品已經(jīng)被其它產(chǎn)品替代的時(shí)候,它的生命就終結(jié)了,幾乎所有軟件產(chǎn)品都會(huì)經(jīng)歷從開始到結(jié)束的演化過程。但存在了很長(zhǎng)時(shí)間的大型企業(yè)級(jí)系統(tǒng)卻會(huì)隨著時(shí)間的推移,向不可維護(hù)、變僵硬的趨勢(shì)發(fā)展。這導(dǎo)致了軟件開發(fā)的停滯,使得響應(yīng)客戶需求的時(shí)間變長(zhǎng)。
本文說明了如何使用MDSD方法來決這些問題。文章首先介紹了我們要解決的問題——向后兼容性問題和升級(jí)問題,說明了為什么這些問題很難解決,然后描述了在當(dāng)前的軟件體系結(jié)構(gòu)中容易隱藏非功能性關(guān)注面的地方。
文章中舉了三個(gè)例子,用來說明MDSD技術(shù)如何幫助我們?cè)诓粨p失靈活性的前提下,解決軟件生命周期問題。這些例子都是來自于電子保健行業(yè)中的真實(shí)敏捷項(xiàng)目,作者還說明了其中的最佳實(shí)踐可以怎樣應(yīng)用于其他情況下。
項(xiàng)目中得到的經(jīng)驗(yàn)教訓(xùn)被總結(jié)為拇指規(guī)則(在面對(duì)復(fù)雜環(huán)境時(shí)候采用的一種簡(jiǎn)化或經(jīng)驗(yàn)性的處理方式,因?yàn)槔硇缘奶幚砟芰κ怯邢薜模�,在文章的最后,引用了一些有用的框架與工具,它們是目前解決方案的基礎(chǔ)。
介紹
在軟件工程領(lǐng)域,經(jīng)過幾年的實(shí)踐,模型驅(qū)動(dòng)的軟件開發(fā)(MDSD)已經(jīng)證明了它并非是曇花一現(xiàn)。如今,MDSD的很多承諾[1]已經(jīng)變成現(xiàn)實(shí),大量成功的例子也持續(xù)涌現(xiàn)出來。
本文說明了可以如何使用模型驅(qū)動(dòng)方法來解決當(dāng)前軟件系統(tǒng)中的一些問題。牢記MDSD最佳實(shí)踐[2],我們將關(guān)注軟件生命周期本身這個(gè)非功能性需求。
軟件生命周期
幾乎所有的現(xiàn)代軟件系統(tǒng)都面臨著生命周期問題。當(dāng)產(chǎn)品發(fā)布或是部署時(shí),你不得不解決向后兼容以及遷移策略等問題。我們通常會(huì)低估產(chǎn)品兼容性問題,或是在開發(fā)中完全忽略這個(gè)問題。當(dāng)我們不得不花費(fèi)很多資源來解決產(chǎn)品兼容性問題時(shí),才會(huì)后悔莫及。嚴(yán)肅地看待這個(gè)問題會(huì)影響到軟件產(chǎn)品的演進(jìn),正如我們將在下面這個(gè)例子中所看到的。
案例:java.lang.Cloneable
Java中的java.lang.Cloneable接口說明了向后兼容性是如何限制軟件演化的。該接口在JDK 1.0時(shí)被引入,僅作為一個(gè)標(biāo)記,并不提供任何接口方法,想要支持克隆的類型都需要實(shí)現(xiàn)這個(gè)接口。
此外,它還需要提供 java.lang.Object.clone()方法合適的實(shí)現(xiàn)。其實(shí)clone()方法如果定義在java.lang.Cloneable接口中似乎更有說服力,并且更自然。但事實(shí)上該接口從未改變過,因?yàn)槟菍?huì)破壞向后兼容性。引入克隆接口方法會(huì)導(dǎo)致對(duì)所有已實(shí)現(xiàn)該接口的類的編譯錯(cuò)誤。關(guān)于這個(gè)bug的完整歷史,請(qǐng)參看[3],對(duì)于java.lang.Cloneable更詳細(xì)的討論,請(qǐng)參看[4]。
在分析我們系統(tǒng)中已經(jīng)存在的生存周期問題細(xì)節(jié)以前,非常有必要建立一個(gè)公共術(shù)語表。
向后兼容性
如果出現(xiàn)以下情況,那么可以認(rèn)為該系統(tǒng)是向后兼容的:
- 它能處理以前系統(tǒng)的接口
- 它能處理以前系統(tǒng)的數(shù)據(jù)
- 該系統(tǒng)的使用者不必關(guān)心系統(tǒng)版本改變
系統(tǒng)的修改可能引發(fā)向后不兼容的問題。通過引入兼容性層可以解決這個(gè)問題,該層負(fù)責(zé)提供以前系統(tǒng)接口的視圖。在這樣的環(huán)境中,權(quán)衡并設(shè)計(jì)進(jìn)-出良好的接口更加重要。
關(guān)于二進(jìn)制與源代碼的兼容性在本文中不做討論,[5]中描述了它們和Java語言之間的差異。
更新
更新描述的是對(duì)已有軟件的改進(jìn)。通過一次更新,應(yīng)用的特性集是不會(huì)被擴(kuò)展的。更新的主要目的在于提供額外的健壯性(例如修復(fù)安全隱患)。更新不會(huì)涉及到接口與數(shù)據(jù)描述的改變,它不會(huì)引發(fā)向后兼容性問題。OSGi版本控制體系[6]定義了版本分類模式major.minor.micro。一次更新只增加第三位,即micro位的版本號(hào)。
升級(jí)
我們認(rèn)為一個(gè)新版本,或?qū)σ延熊浖黾庸δ芏际且淮紊?jí)。與更新不同的是,升級(jí)提供了新的特性集。在這個(gè)過程中兼容性問題或多或少都會(huì)暴露出來,這取決于新老版本之間的差異大小。讓我們?cè)賮砜匆幌翺SGi版本控制體系,其中的minor版本號(hào)的增加描述了一次向后兼容的升級(jí)。Major發(fā)布號(hào)的增加則意味著一次向后不兼容的升級(jí)。
遷移
遷移是將軟件系統(tǒng)從一個(gè)操作環(huán)境移動(dòng)到另一個(gè)操作環(huán)境的過程。對(duì)于軟件系統(tǒng)而言,遷移一般是將軟件系統(tǒng)移動(dòng)到一個(gè)更新的版本(忽略了將現(xiàn)有軟件產(chǎn)品替換為競(jìng)爭(zhēng)產(chǎn)品的遷移場(chǎng)景)。如果這個(gè)新版本承諾了對(duì)老版本是向后兼容的,那么對(duì)于消費(fèi)者而言遷移過程是不需要任何額外操作的。另一種情況就是消費(fèi)者需要為遷移做出附加操作以適應(yīng)新版本。對(duì)于此種情況,向后不兼容將影響該產(chǎn)品的所有客戶。越多客戶使用該產(chǎn)品,對(duì)于遷移帶來的兼容變更過程就越痛苦。隨著產(chǎn)品相關(guān)客戶的增加,向后不兼容變更的成本與后續(xù)遷移過程代價(jià)會(huì)變得非常高。因此,對(duì)于向后兼容性問題的正確考慮對(duì)軟件系統(tǒng)演化起著至關(guān)重要的作用。
MSSD,盛裝的騎士
如果您已經(jīng)在使用模型驅(qū)動(dòng)的方法進(jìn)行產(chǎn)品開發(fā),那么在這里你會(huì)找到很多能夠減少我們前面提到的沖突的方法。我們的主要目的就是為了讓客戶滿意,并且不受系統(tǒng)變更的影響,同時(shí)還可以讓產(chǎn)品繼續(xù)發(fā)展,而不會(huì)感到在你的開發(fā)瓶頸處有里程碑的存在。
在每個(gè)現(xiàn)代應(yīng)用中,我們都可以識(shí)別出一些潛在的向后不兼容的情況。有了MDSD方法,我們就擁有了消除這些阻力的工具。以下的三個(gè)示例說明了在當(dāng)前的軟件體系結(jié)構(gòu)中,生存周期方面的問題隱藏在何處,以及如何使用模型驅(qū)動(dòng)方法來讓軟件系統(tǒng)的兼容性處于控制之下。
API 的不兼容性
每個(gè)暴露給外部的API都聲明了提供者與其編程接口的潛在消費(fèi)者之間的契約。使用新的不兼容的API替換現(xiàn)存的API將會(huì)導(dǎo)致正在使用舊API的客戶端死鎖。這不僅只是針對(duì)類似Java的普通編程語言,對(duì)于所有暴露接口的形式(例如Web服務(wù))都一樣。
API的不兼容性可以通過一個(gè)專門的向后兼容層來解決。當(dāng)軟件系統(tǒng)無法支持以前版本API,而只是暴露最新版本的API時(shí),我們可以在目標(biāo)系統(tǒng)(客戶端系統(tǒng))前部署向后兼容層,它會(huì)提供當(dāng)前軟件系統(tǒng)中所有版本的API。對(duì)于這個(gè)向后兼容層到底是以中間件ESB(企業(yè)服務(wù)總線)的形式部署還是直接部署于目標(biāo)系統(tǒng)中我們不做深究。向后兼容層在此扮演了一個(gè)API門面的角色,它在各API版本間進(jìn)行消息轉(zhuǎn)換[7]。有了這個(gè)架構(gòu),我們就需要制定消息轉(zhuǎn)換的規(guī)則。
對(duì)于每個(gè)不兼容的API都必須定義對(duì)應(yīng)的消息轉(zhuǎn)換規(guī)則,這是MDSD的關(guān)鍵所在。在重要項(xiàng)目中,我們應(yīng)該將定義消息轉(zhuǎn)換規(guī)則視作正常處理而非異常處理。因此,編寫轉(zhuǎn)換代碼是經(jīng)常性的并且是非常關(guān)鍵的任務(wù)。如果遺漏了一項(xiàng)轉(zhuǎn)換,那么就會(huì)導(dǎo)致無法使用以前對(duì)應(yīng)版本的API接口。如果遵循模型驅(qū)動(dòng)方法,API接口應(yīng)該定義為一個(gè)模型。對(duì)于軟件系統(tǒng)的每個(gè)版本都對(duì)應(yīng)一個(gè)模型。當(dāng)比較不同版本的API接口時(shí),可以通過對(duì)比它們對(duì)應(yīng)的模型實(shí)例看出差異。由此您可以得到所謂的系統(tǒng)差異模型,其包含了兩個(gè)系統(tǒng)版本間的所有差異。以該差異模型作為輸入,可以生成一份可讀的變更報(bào)告,該報(bào)告描述了兩個(gè)系統(tǒng)版本的細(xì)節(jié)差異。以該報(bào)告作為檢查清單,您可以控制系統(tǒng)發(fā)生的所有改變。而不會(huì)再忽略了任何一個(gè)變更或是遺漏了編寫該變更對(duì)應(yīng)的轉(zhuǎn)換代碼。對(duì)于一組接口的變更處理和上面是類似的。有了差異模型,您可以更順利地處理后續(xù)項(xiàng)目任務(wù)。另外,對(duì)于轉(zhuǎn)換通常是給轉(zhuǎn)換數(shù)據(jù)添加一個(gè)必須的類型字段,以標(biāo)識(shí)出該消息需要轉(zhuǎn)換成的類型。對(duì)于同一類消息的轉(zhuǎn)換而言,這項(xiàng)工作是非常乏味、重復(fù)的,最好將其自動(dòng)化。在沒有模型驅(qū)動(dòng)支持時(shí),開發(fā)者不得不手工編寫所有轉(zhuǎn)換代碼,但當(dāng)我們擁有整個(gè)變更信息時(shí),通過模型驅(qū)動(dòng)的方式,可以以某個(gè)確定的標(biāo)識(shí)模式生成消息轉(zhuǎn)換代碼。這可以將開發(fā)人員從重復(fù)、易出錯(cuò)的任務(wù)中解放出來,將他們的時(shí)間投在其他更值得去做、更有挑戰(zhàn)性的任務(wù)上。當(dāng)然,模型驅(qū)動(dòng)的方法也是有局限性的。100% 生成轉(zhuǎn)換代碼只是一種烏托邦式的期待。在模型中總有一些復(fù)雜變更不能標(biāo)識(shí)到模式目錄。對(duì)于這樣的情況,轉(zhuǎn)換只能手工編寫。因此,我們需要時(shí)刻謹(jǐn)記區(qū)分自動(dòng)生成與手工編寫變換代碼的最佳實(shí)踐[2]。說到這里,您可能會(huì)問“為什么我們要付出如此多的努力在向后兼容上?”,這個(gè)問題問到點(diǎn)上了,而且必須針對(duì)每一種不同產(chǎn)品的具體情況來回答。對(duì)于歷史遺留系統(tǒng),您沒有選擇,必須保持向后兼容。這點(diǎn)在電子保健領(lǐng)域印證無疑。有很多醫(yī)療機(jī)構(gòu)需要與中心外部系統(tǒng)保持通信,而這些中心系統(tǒng)一般都非常古老,并且希望有穩(wěn)定的接口與其通信。改變目標(biāo)接口,那么每個(gè)醫(yī)療機(jī)構(gòu)都必須做出相應(yīng)的修改。投入一些時(shí)間來解決目標(biāo)端的不兼容性沖突比想辦法移除所有異構(gòu)遺留環(huán)境更為合理。
數(shù)據(jù)庫模式出軌
通過使用數(shù)據(jù)關(guān)系映射工具(例如Hibernate)對(duì)數(shù)據(jù)庫進(jìn)行抽象既是一種賜福也是一種詛咒。一方面,它為開發(fā)者提供了想要的數(shù)據(jù)庫技術(shù)抽象;另一方面也隱藏了對(duì)不同版本間數(shù)據(jù)庫模式的出軌(不匹配)。對(duì)于每一次持久化模型的變更都將重新處理已部署應(yīng)用的數(shù)據(jù)庫模式。例如給領(lǐng)域?qū)ο筇砑右粋€(gè)新的持久化字段就需要給對(duì)應(yīng)的數(shù)據(jù)庫表添加一個(gè)新的列。如果持久化模型與數(shù)據(jù)庫模式不同步,那我們就認(rèn)為數(shù)據(jù)庫模式出軌了。
>而MDSD方法保證了數(shù)據(jù)庫模式不會(huì)出軌。如果我們以持久化模型作為主模型,根據(jù)該模型生成需要的數(shù)據(jù)庫模式,那么我們就能夠重用模型信息以處理兼容性問題。假設(shè)我們有不同的模型版本,我們就可以生成差異模型,差異模型會(huì)為我們提供非常有用的不同版本差異的信息。就像前面那樣,我們可以生成一份變更報(bào)告,來查看變更影響。另外,我們還可以基于差異模型生成SQL升級(jí)腳本。我們不打算100%生成腳本,那不現(xiàn)實(shí)。對(duì)于復(fù)雜數(shù)據(jù)庫模式情況,生成的腳本總是需要手動(dòng)添加一些代碼的。自動(dòng)生成部分與手工編寫部分的組合定義了數(shù)據(jù)庫模式從一個(gè)舊的版本升級(jí)到一個(gè)新的版本。由于存在各式各樣的SQL方言,所以提供已支持?jǐn)?shù)據(jù)庫的對(duì)應(yīng)升級(jí)腳本是很重要的。如果系統(tǒng)支持多個(gè)數(shù)據(jù)庫,那么這些腳本必須遵從對(duì)應(yīng)方言。對(duì)于Ruby遷移[8]而言,Ruby社區(qū)提供了不依賴數(shù)據(jù)庫(database-agnostic)的DSL來描述數(shù)據(jù)庫模式變更。這些數(shù)據(jù)庫模式變更描述將被翻譯成不同方言的SQL語句。與直接生成SQL相比,模型驅(qū)動(dòng)方法能夠?qū)⒉町惸P妥儞Q成Ruby遷移表達(dá)。這樣一來,生成SQL語句的責(zé)任就轉(zhuǎn)移到了Ruby遷移。隨著MDSD提供的支持,對(duì)于領(lǐng)域模型變更帶來的恐懼消失殆盡。開發(fā)者可以不再猶豫對(duì)領(lǐng)域模型的演進(jìn),項(xiàng)目重獲敏捷。
語言變更
當(dāng)使用模型驅(qū)動(dòng)的技術(shù)時(shí),我們經(jīng)常使用一些領(lǐng)域特定語言(DSLs)來描述我們的模型。這些領(lǐng)域特定語言非常適合描述目標(biāo)領(lǐng)域,因?yàn)樗鼈優(yōu)槲覀兲峁┝藢?duì)所需要的領(lǐng)域特定的表達(dá)與抽象。與一些通用意圖語言(general purpose languages,GPLs)相比,DSLs不會(huì)像GPLs那樣死板,并且不受語言變更約束。隨著DSL描述的領(lǐng)域與元模型的演化,一些新增的概念與結(jié)構(gòu)體(constructs)將被添加或替換,語言本身也在演化。不幸的是,領(lǐng)域特定語言本身對(duì)于不兼容性是沒有任何免疫力的。
而對(duì)于潛在的沖突還是有辦法解決的。我們可以使用文獻(xiàn)[9]中提到的反腐化層(Anticorruption Layer)模式處理這類沖突。就像其名字一樣,該模式可以解決腐化問題。在一個(gè)語言變更場(chǎng)景中,腐化發(fā)生于我們變更語言元模型的地方。對(duì)于新版本元模型而言,所有滿足舊的元模型的模型都腐化了。遺留的元模型不再被系統(tǒng)支持,并且客戶端將被強(qiáng)制遷移到基于新元模型的語言上。通過特定的反腐化層,我們既能保護(hù)舊的元模型,也能讓元模型朝著更合理的方向演化。反腐化層是一系列門面、適配器以及描述不同模型變換的變換器的組合�?蛻舳藢�(duì)于元模型可見的同時(shí),我們也維護(hù)這一個(gè)內(nèi)部元模型。該內(nèi)部元模型作為主元模型將被用于未來的模型處理。所有的外部模型實(shí)例將被變換成對(duì)應(yīng)內(nèi)部元模型的模型實(shí)例。外部元模型提供了目標(biāo)領(lǐng)域的特定視圖。通過反腐化層,我們可以將多個(gè)視圖正確映射到一個(gè)通用的元模型上。此時(shí),不同版本變換可以在反腐化層內(nèi)部被清晰地分離出來,元模型過時(shí)策略也可以被更容易地實(shí)現(xiàn)。
這里有個(gè)簡(jiǎn)單的例子展示了反腐化層所帶來的好處:假設(shè)您用自己的DSL管理您全世界的豪華汽車車庫容量。在版本1中,您為每一輛車指定了寬度、高度以及長(zhǎng)度。幾個(gè)月后,你發(fā)現(xiàn)您的車庫里只停了三種尺寸的車輛:S、M以及L,因此,新版本的倉庫尺寸描述替代了三維描述。您沒有將新版本尺寸描述馬上上線,而是實(shí)現(xiàn)了一個(gè)反腐化層,該層仍然理解原來版本1的三維元模型,只是在內(nèi)部將其變換為版本2的尺寸元模型。
拇指規(guī)則
就像您在示例中看到那樣,我們經(jīng)常面對(duì)各種不兼容性問題。當(dāng)結(jié)合MDSD處理兼容性問題時(shí),本文得出如下規(guī)則:
向后兼容性是用戶友好的
流行的頂尖應(yīng)用將帶來更多的客戶。委托客戶增加時(shí),您必須應(yīng)對(duì)隨之而來的更多的需求。因此,成功的應(yīng)用將進(jìn)一步開發(fā)后續(xù)版本。以往已經(jīng)有足夠多的失望之極的客戶與不兼容性作斗爭(zhēng)的反面示例,所以解決兼容性問題將得到客戶非常高的滿意度評(píng)價(jià),可以將客戶牢牢拉攏在您的解決方案上。滿意的客戶是長(zhǎng)期系統(tǒng)運(yùn)維的關(guān)鍵動(dòng)力。
向后兼容性是昂貴的
兼容性不是免費(fèi)的。生存周期關(guān)注面猶如一個(gè)沉睡的巨人,架構(gòu)師通常不會(huì)去喚醒它們。一旦它們被喚醒,特別是在開發(fā)后期,我們需要花費(fèi)巨大的代價(jià)來控制它。通過模型驅(qū)動(dòng)技術(shù),兼容性關(guān)注面可以在一開始就被評(píng)估出并可以進(jìn)行正確的整合處理。因此,兼容性問題不再限制了成功產(chǎn)品的演化。使用增量式遷移能夠保持低成本、可管理的復(fù)雜度:只提供直線遷移路徑替代提供所有支持版本遷移路徑。遷移鏈完成后,您一樣可以達(dá)到同樣的遷移目標(biāo)。
不兼容性不可避免
兼容性問題在所有的軟件產(chǎn)品中都需要考慮。對(duì)于內(nèi)部產(chǎn)品而言,使用范圍與兼容性可以被控制,而其他產(chǎn)品則需要避免發(fā)生兼容性問題。項(xiàng)目需要有著保持向后兼容的能力以及可以朝著向需要方向進(jìn)行合并的生命力。只有這樣才能讓產(chǎn)品長(zhǎng)期存活。因此,不要等兼容性問題發(fā)生時(shí)才后悔,對(duì)于公開開發(fā)的產(chǎn)品更是如此。事實(shí)上,開源開發(fā)者從不知道他們的產(chǎn)品將被如何使用,在何地使用。
系統(tǒng)一旦發(fā)布,就成了遺留系統(tǒng)
一次發(fā)布版本號(hào)描述了系統(tǒng)生命中的一個(gè)快照。一旦發(fā)布了確定的版本,開發(fā)其實(shí)早已進(jìn)行多時(shí)。因此,該發(fā)布描述了過去的開發(fā)狀態(tài)。當(dāng)產(chǎn)品發(fā)布后,您不得不為了贏得客戶滿意而為其提供支持。
模型差異也是模型
前兩個(gè)示例中強(qiáng)調(diào),模型差異描述了一些有價(jià)值的、可在后續(xù)開發(fā)中利用的信息。在運(yùn)用模型驅(qū)動(dòng)方法后,獲取不同模型版本的差異模型是非常廉價(jià)的。已有工具鏈需要被擴(kuò)展,以產(chǎn)生并處理差異模型。一些環(huán)境(例如版本控制系統(tǒng))對(duì)于維護(hù)已有模型版本是非常有用的。
反腐化層是一種最佳實(shí)踐
反腐化層給我們帶來一種處理兼容性與彈性的有力模式。當(dāng)內(nèi)部與外部聯(lián)系非常緊密時(shí),它清晰地分離了內(nèi)部與外部表示。它所引入到已有工具鏈中的部分是非常小的,并且可以在稍晚時(shí)候引入。一旦整合完畢,它允許所有終端獨(dú)立演化。
有用的框架與工具
Java中的MDSD框架有:openArchitectureWare(oAW)[10],它是一個(gè)“構(gòu)建MDSD/MDA工具的工具”。它是Eclipse模型項(xiàng)目(EMP)的一個(gè)子項(xiàng)目,提供了健壯的模型驅(qū)動(dòng)開發(fā)解決方案。在EMP中,Eclipse建�?蚣埽‥MF)[11]作為主要的構(gòu)建工具提供了頂尖的結(jié)構(gòu)化數(shù)據(jù)模型處理。因?yàn)閛AW非常多地使用了EMF技術(shù),所以我們建議兩者結(jié)合使用。EMF Compare[12]是一個(gè)用于比較EMF模型的工具,看上去它非常適合我們創(chuàng)建有價(jià)值的差異模型。
關(guān)于作者
Andreas Kaltenbach是一名來自InterComponentWare AG(ICW)[13]的軟件開發(fā)者與培訓(xùn)者,他是德國保健相關(guān)應(yīng)用的專家。他圍繞ICW的電子保健框架(eHF),關(guān)注MDSD方法以及安全學(xué)。另外,他還是ICW的開放電子保健基金會(huì)[14](關(guān)注于建立保健相關(guān)行業(yè)的開源社區(qū))的開發(fā)者代表。