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