本文將用盡可能簡單的文字來描述外掛框架原理。很多人以為外掛化很複雜,所以就一直將這類框架阻擋在門外。實際上,在我們的實踐過程中,從框架的使用角度來看,它非常簡單,我們團隊裡面非正規院校畢業的女生也可以來實際使用。如果說外掛框架難的地方,我反倒覺得克服人的天然惰性更加困難。我們不能習慣於墨守成規,日復一日年復一年,按照相同的模式來開發,將自己打造成一部“編碼機器”,成為沒有價值的“程式猿/媛”。使用外掛框架,沒有多少技術難點,不過需要我們提升我們的軟體開發思想,改變現有開發方式。
1 外掛框架本質
在.NET平臺,一個程式是由“程式集 + 資源”構成的。程式集是由我們開發的一個個的類。這些類可能是通用功能的輔助類、資料訪問類、業務邏輯類,也可以是WinForm應用的窗體類或者Web應用的Web窗體類。以傳統模式開發的程式,一般情況下,不管是我們開發的程式集,還是引用的第三方程式集,它們都在應用程式的bin目錄下,如下所示。
外掛化開發方式與傳統方式不同在於,它會把程式集按照一定結構進行組織,比如下面這個程式是基於外掛化方式來構建的,不同功能的程式集則組織到Plugins目錄下,如下所示。
此時,bin目錄則僅僅包含幾個很通用的程式集。
在Plugins目錄下,則按照功能進行分組,每一個目錄為一個外掛,它實現一組功能。
下面我們來看其中一個外掛——AlarmManagementPlugin外掛的目錄結構,如下所示,你會發現,外掛擁有自己獨立的bin目錄,在自己的bin目錄下,放置著這個功能涉及的程式集。
在傳統開發方式中,放在bin目錄下的程式集會由.NET類載入器按需去載入,但是當我們要實現外掛化方式開發時,需要依賴於外掛框架實現從不同的外掛目錄中載入程式集。因此,外掛框架本質上是擴充套件了.NET類載入器的功能,使其能夠從外掛目錄中載入程式集。
2 進一步看外掛框架
外掛化開發方式不僅僅從程式集的組織方式上發上了變化,更重要的是,在功能的組織和實現也發生了變化。我們用一個非常典型的分層架構看看二者區別。
下圖是一個分層架構的應用程式,由表示層、業務層、資料層等組成,每一個層次都有相對應的功能組成,在表示層,我們一般是構建了一個主介面,然後由不同的開發者在主介面上直接放置上選單及選單點選事件的響應,同理,其它層次也類似,不同開發者根據需要實現的功能來新增不同的程式碼。
在這種模式下,所有程式設計師開發的不同層次的功能程式碼一般都在同一個程式集裡面,在開發過程中,團隊需要不停的進行合併,並執行整合測試。下圖是大家都很熟悉的PetShop的專案結構。這裡面BLL程式集(專案)放置的是業務邏輯層的程式碼,Web程式集(專案)放置的是表示層程式碼,DALFactory及涉及的其它DAL程式集則是資料訪問層的相關程式碼。
下面我們來看看一個功能所涉及的程式集。這意味著,所有程式設計師都需要獲取到專案的程式碼,然後遵循規則,在各個專案中新增自己的程式碼,再將這些程式碼合併起來,最後完整編譯再交付軟體系統。
現在我們來看看使用外掛化開發方式的組織方式,如下圖所示。
在這種模式下,功能被封裝到外掛中,一個程式設計師負責若干個外掛,每一個外掛由其相應功能涉及的介面、業務邏輯和資料訪問等程式碼組成。每一個外掛可以獨立開發、測試和部署,開發者間不再需要對程式碼進行合併。下圖是一個外掛化應用程式的專案。
在這裡,每一個外掛完成一組功能,它們可以有自己獨立的介面、業務邏輯和資料訪問實現,外掛間具有物理隔離性,開發者可以獨立開發自己的功能,獨立測試、部署與升級,一旦開發完成後,可以由外掛框架在進行組合,不再需要進行程式碼合併和整體釋出。
3 從簡單示例看外掛化
現在我們看看使用外掛化來開發的示例。下圖是該軟體的整合後的介面,有5個一級選單,其中“伺服器”為GPRS通訊伺服器外掛定義,由程式猿A開發;“基礎資料、能耗分類、安裝資料”選單為基礎資料管理外掛定義,由程式猿B開發。
下圖是“伺服器”的子選單和其中一個頁面。
下圖是“基礎資料”的子選單和其中一個頁面。
該系統使用外掛化開發方法如下。
(1)該系統使用了一個通用介面框架外掛,你可以從iOpenWorks網站來下載到該外掛(http://www.iopenworks.com/Products/ProductDetails/Introduction?proID=386),這個介面框架外掛提供了一個空白的介面,這個介面支援三級選單,允許我們通過以下配置將自己定義的選單及選單對應的窗體註冊到主介面。如下XML定義是由GPRS伺服器外掛來定義的。它配置了一個一級選單和兩個二級選單,以及對應的兩個使用者控制元件。
<Extension Point="UIShell.WpfShellPlugin.LinkGroups"> <LinkGroup DisplayName="伺服器" DefaultContentSource="UIShell.EcmCommServerPlugin.CommServerUserControl"> <Link DisplayName="通訊伺服器" Source="UIShell.EcmCommServerPlugin.IntegratedCommServerUserControl" /> <Link DisplayName="測試伺服器" Source="UIShell.EcmCommServerPlugin.CommServerUserControl" /> </LinkGroup> </Extension>
(2)程式猿A建立一個自己的專案來開發通訊伺服器,獨立的實現具體的應用和業務邏輯,其專案結構如下所示。在這裡,他分成兩個專案來實現GPRS通訊伺服器,一個是介面表示層UIShell.EcmCommServerPlugin專案,另一個是業務邏輯層UIShell.EcmCommServerService專案。在開發過程中與程式猿B互相獨立,他們可以獨立開發、測試、部署和維護。
執行這個專案後,程式猿A得到以下的結果。
(3)同理,程式猿B也建立一個自己的專案來開發基礎資料管理,獨立的實現具體的應用和業務邏輯,其專案結構如下所示。在這裡,他分成兩個專案來實現,一個是介面表示層UIShell.EcmConfigurationPlugin專案,另一個是業務邏輯層UIShell.EcmDomainService專案,該專案可以被A來重用。在開發過程中與程式猿A互相獨立,他們可以獨立開發、測試、部署和維護。
執行這個專案後,程式猿B得到以下的結果。
(4)程式猿A和B開發到一個階段的時候,就可以來隨時釋出自己的外掛,點選專案右鍵,直接將外掛釋出到外掛倉庫。
以下是釋出的結果,該專案的外掛倉庫由3個專案組成。
通訊伺服器專案的外掛列表如下。
配置管理專案的外掛列表如下所示。
(5)測試人員/部署人員可以通過外掛管理來獲取到這兩個程式猿開發的外掛,然後將這些外掛下載組裝起來。
下載安裝後,就是如下的效果了。
4 外掛化有什麼好處
從以上簡單的例子,我們看到,外掛化最直接的好處就是可以以模組化的方式來獨立並行構建軟體系統,在構建的過程中可以隨時進行整合。下面我把使用外掛化的優點總結一下:
(1) 外掛化優化了團隊協作,避免團隊開發過程中互相交叉,不再需要更改各自的程式碼將開發的成果整合到一起;
(2) 使用外掛化開發後,每一個人的工作都非常的獨立,可以有獨立的架構、獨立開發、獨立測試、獨立部署、獨立升級,並行構建;
(3) 外掛化使得重用度更高,在我們開發的一個專案中,超過50%的模組都直接重用了原有的結果,不需要更改任何的程式碼;
(4) 外掛化使開發和維護更加簡單,這也是得益於每一個開發人員可以單獨開發自己的應用,每個人都很專注,並且只需要關注系統中很小的一部分,在以上示例中,每一個開發人員能看到的程式碼都是自己來開發的;
(5) 使用外掛化開發,我們的釋出、升級也很簡單,右鍵釋出到外掛倉庫,部署測試人員通過外掛管理介面來下載每一個人的成果,組裝成軟體,並且可以隨時升級,這避免了很多手工釋出、整合;
(6) 使用外掛化,可以很容易的構建自己的知識庫,是完全可複用,並持續不斷改進和增長。
5 外掛框架原理
從上述小節,我們看到,外掛化開發更多的是軟體方法和思想上的改變。為了滿足這種開發方法的團隊協作模型,外掛框架需要來解決一下幾個問題:
5.1 如何實現模組化
實現模組化,意味著我們需要把功能相關的類組織到若干獨立的程式集,這些程式集是在外掛目錄下。因此,實現模組化就必須解決以下幾個問題:
(1)正確的類載入:即外掛框架必須對CLR類載入進行擴充套件,使其能夠從外掛目錄中正確載入到外掛程式集;
(2)外掛描述:必須引入一個外掛描述方法,它來告知當前外掛的基本資訊、版本標識、以及這個外掛所包含的程式集;
(3)解決外掛的依賴關係:在一個實際應用系統中,必然存在相互依賴。在這裡,依賴是指一個外掛使用了另一個外掛定義的型別或者建立的物件。也就是說,我們可以直接使用另一個外掛定義的型別,在更加麻煩的場景中,還存在迴圈依賴。外掛框架夠必須很好的處理好外掛依賴關係的解析與管理。只有當所有的依賴關係都滿足後,一個外掛才能夠對外暴露功能;
(4)類載入空間定義:外掛既有自己的程式集,也可以依賴其它外掛的程式集,那這時候就必須對外掛的型別空間做一個唯一的定義,保證當前外掛能夠載入的型別獨立性和隔離性;
(5)外掛多版本問題:由於一個外掛應用系統中的外掛是由若干團隊開發,每一個團隊獨立開發自己的外掛,很有可能一個外掛使用了NLog的1.0版本呢,而另一個團隊則更新到最新的1.1版本,這時候,我們必須保證兩個團隊的外掛能夠正確執行;
(6)外掛狀態的定義:外掛可以具備不同的狀態,並在不同狀態下有不同的體現。最常用的有:安裝、解析、正在啟動、啟用、正在停止、解除安裝等,當外掛處於解析狀態表示這個外掛所有依賴關係都已經滿足,可以被正常啟動;當外掛處於啟用狀態則表示這個外掛已經被啟動,所有功能都已經暴露了;
(7)外掛啟動初始狀態和啟動順序:當外掛被框架載入時,外掛處於什麼狀態;用什麼樣的方式來設定外掛的啟動順序;當外掛有依賴關係時,這時候對啟動順序又有什麼影響;
(8)外掛的初始化和終止處理:即相當於外掛的入口和出口,當外掛被啟動或停止需要執行的操作是如何來定義的。
5.2 如何實現模組通訊
外掛框架必須解決模組通訊。傳統的通訊,我們都是基於CLR類載入來實現的,即我們可以直接引用另一個程式集的型別,比如:ClassA cls = new ClassA();或者ClassA.Singleton.SayHello()。外掛框架可以提供兩種通訊方式,如下:
(1)傳統通訊方式:即我們可以像以前一樣,直接通過引用型別來進行通訊,或可以通過動態載入型別使用反射來通訊;
(2)面向服務:基於SOA模型來實現通訊,即服務 = 服務契約(介面) + 實現(類),服務提供商將服務註冊到匯流排,服務消費者使用服務契約從服務匯流排獲取服務,繫結使用。
5.3 如何實現模組擴充套件
模組擴充套件指的是在不更改已經發布的外掛的任何程式碼的情況下,來變更該外掛的功能。這也是外掛重用的支撐之一。在OSGi.NET外掛框架,使用了基於ExtensionPoint-Extension機制,暴露擴充套件點的外掛可以被擴充套件,擴充套件的外掛通過XML來定義Extension的內容,從而被暴露擴充套件點的外掛來獲取,並變更其功能。
5.4 外掛框架高階支援
除了上述的三大基礎功能是外掛框架需要實現的,它還可以提供以下更高階的功能:
(1)對自動升級的支援:外掛框架可以支援外掛的更新,當發現有新版本在外掛框架內部時,可以應用更新;此外,還可以對外掛框架本身實現自動更新;
(2)對動態性的支援:外掛框架可以支援動態安裝、啟動、停止、更新和解除安裝外掛,允許靈活控制整個應用系統;
(3)對遠端管理的支援:可以通過遠端管理的API,來實現外掛核心的瀏覽和管理,從而實現核心情況的檢視和外掛的遠端管理控制;
(4)對外掛倉庫的支援:可以將外掛釋出到外掛倉庫,而外掛框架則可以利用外掛外掛倉庫來實現動態安裝和更新,有利於應用系統的知識積累、釋出和團隊協作;
(5)對DevOps的支援:外掛開發者可以一鍵釋出更新,而測試人員和部署的應用,則可以及時下載到更新。
6 OSGi.NET與其它外掛框架的比較
一個好的外掛框架不能有太多的侵略性,如果有太多侵略性的話,我們需要了解特別多的框架API,意味著我們在開發過程中動不動就得和框架打交道,這非常不利於應用外掛化開發。OSGi.NET框架之所以能夠成為一個通用的外掛框架產品,主要原因在於它的低侵入性。有很多外掛框架,它基本上是和UI、資料訪問等操作嚴格繫結起來的,要應用這些框架時,你需要了解框架關於外掛化、UI、資料訪問等操作的API,需要完全掌握這些API並在專案中與這些API打交道,更麻煩的是,一旦這些API無法滿足時,這個框架便難以再使用。OSGi.NET外掛框架沒有像其它框架這麼來幹了,它把所有功能都當前外掛,都可以被定製或者替代,這裡,我們可以根據需要來定製和安裝介面框架外掛、資料訪問外掛等,使用這些外掛時,都不再需要與框架API打交道了。
下面我來用幾個指標詳細的介紹OSGi.NET與其他外掛框架比較。
目前在.NET平臺,除了OSGi.NET外掛框架之外,流行的外掛開發平臺有SharpDevelop核心、微軟的MEF/MAF/SCSF、思科的Mono.Addins。這些框架詳細比較如下表所示。
比較項 |
OSGi.NET |
SharpDevelop |
MEF |
MAF |
Mono.Addins |
SCSF |
Web開發 |
支援 |
不支援 |
不支援 |
不支援 |
不支援 |
不支援 |
執行緒安全 |
支援 |
不支援 |
不支援 |
不支援 |
不支援 |
不支援 |
國際規範 |
OSGi R4 |
無 |
無 |
無 |
無 |
無 |
多版本 |
支援 |
不支援 |
不支援 |
支援 |
不支援 |
不支援 |
外掛倉庫 |
支援 |
不支援 |
不支援 |
不支援 |
支援 |
不支援 |
依賴管理 |
支援 |
支援 |
不支援 |
不支援 |
支援 |
不支援 |
面向服務 |
支援 |
弱 |
不支援 |
不支援 |
不支援 |
支援 |
動態性 |
支援 |
很弱 |
不支援 |
弱 |
較弱 |
不支援 |
晚載入 |
支援 |
不支援 |
不支援 |
支援 |
支援 |
不支援 |
擴充套件性 |
強 |
強但抽象 |
強 |
強但複雜 |
強 |
不支援 |
隔離性 |
強 |
弱 |
很弱 |
強但跨域 |
強 |
很弱 |
可移植性 |
強 |
強 |
差 |
差 |
強 |
差 |
易用性 |
強 |
一般 |
強 |
差 |
強 |
差 |
輔助支援 |
強,模板、打包工具、遷移工具、遠端控制、除錯 |
無 |
無 |
無 |
很弱,打包工具 |
弱,IDE模板 |
.NET框架 |
.NET 2.0+ |
.NET 2.0+ |
.NET 4.0+ |
.NET 1.0+ |
.NET 2.0+ |
.NET 2.0+ |
在目前所有流行的外掛框架中,OSGi.NET框架是唯一支援Web和執行緒安全的框架,同時OSGi.NET框架能夠支援所有.NET應用,它使我們可以在ASP.NET、ASP.NET MVC、WPF、WinForm、控制檯、Windows服務、移動框架中使用統一的開發模式。
從規範化角度來看,只有OSGi.NET外掛框架是遵循OSGi R4規範來設計的,其它框架均使用自己的方法來開發。OSGi R4規範是由OSGi聯盟在1999年提出的服務平臺規範,Eclipse 3.0以後其核心使用OSGi規範來構建,從此OSGi得到快速發展,在IDE、Web容器、雲端計算等複雜領域中得到成功應用。
對於大規模複雜應用,多版本是必須具備的功能,因為有可能兩個團隊開發的功能會使用同一個第三方庫的不同版本。目前只有OSGi.NET框架和MAF框架支援多版本,允許在同一個應用系統中載入不同版本的庫。MAF框架對多版本的支援是通過使用AppDomain來實現,因此外掛間需要跨域呼叫;OSGi.NET框架對多版本的支援是通過對CLR底層類載入機制進行擴充套件,無需跨域呼叫,具備很高的效能,也是支援Web等其它應用開發的保證。
外掛倉庫可以用於管理外掛的不同版本,並實現外掛的升級和動態安裝,在這些框架中只有OSGi.NET外掛框架和Mono支援外掛倉庫。不過,Mono的外掛倉庫僅實現外掛的檔案管理,而OSGi.NET的外掛倉庫則從專案開發、管理、釋出、維護的角度出發,構建了基於外掛倉庫的軟體生產線平臺。OSGi.NET外掛倉庫不僅僅實現外掛的各個版本管理,除此之外,它提供了專案管理、共有外掛倉庫、私有外掛倉庫、專案成員及許可權、外掛核心版本管理等,適合各個團隊的統一協作,是一個創新性的軟體生產線平臺。
外掛式應用開發,一個外掛會引用另一個外掛的類,也就意味著外掛框架需要對這種依賴關係提供支援並實現管理。MEF、MAF和SCSF沒有提供這類功能,SharpDevelop、Mono.Addins和OSGi.NET框架提供了完善的依賴管理,其中OSGi.NET對迴圈依賴提供了更好的支援並且在外掛倉庫中也實現依賴管理,因而在下載安裝外掛時,可以自動下載安裝必要的外掛。
面向服務開發模型實現了服務提供商和服務消費者的解耦,具有良好的擴充套件性和動態性。OSGi R4規範的服務層對面向服務提供了支援。在這些框架中,OSGi.NET和Mono.Addins對服務提供了支援,SharpDevelop則很弱,其它框架不支援該開發模型。面向服務實現了外掛間的通訊,這種通訊方式是送耦合的方式。
動態性是指在應用系統執行過程中可以動態的安裝、啟動、停止、解除安裝和更新外掛。目前只有OSGi.NET框架具備最全面的動態性支援,它提供了外掛各個生命週期狀態,並提供了相應的API來支援,而相比較只有SD、MAF和Mono.Addins提供比較弱的動態性,他們對這些操作僅提供部分的支援。
晚載入是指在必要的時候才載入外掛的程式集,這可以更好的提高應用程式的啟動速度和節省記憶體開支。目前支援MAF、Mono.Addins和OSGi.NET框架支援晚載入,其中只有OSGi.NET框架支援外掛晚啟用,即外掛的啟用可以推遲到必要的時候。
可擴充套件性是外掛框架非常重要的功能,因為在這裡,外掛一般是封裝好的,對外需要暴露可擴充套件能力,可以通過擴充套件來變更功能。在這些框架中,只有SCSF不支援擴充套件。OSGi.NET和Mono.Addins均提供了靈活且簡單的ExtensionPoint-Extension機制的擴充套件,而SharpDevelop和MAF的擴充套件則非常的抽象且複雜。
隔離性是指外掛具備自己獨立的目錄結構和型別空間。MAF、Mono.Addins和OSGi.NET框架提供了良好的隔離,外掛具備獨立的結構和型別空間,但是SharpDevelop、MEF和SCSF的隔離性則相對來講弱很多。
可移植性指的是可以將傳統的程式碼快速移植成外掛,在外掛框架中使用,目前MEF、MAF和SCSF對移植性支援很差,需要更改很多程式碼才可以在外掛框架中應用,這不利於我們的使用。在SharpDevelop、Mono.Addins和OSGi.NET框架中,他們雖然具備很強可移植性,不過,只有OSGi.NET框架提供了遷移工具,可以在5分鐘內將傳統程式集移植成外掛。
易用性是是否應用外掛框架的重要指標,OSGi.NET框架具備最強的易用性,擁有完善的程式設計工具,並且提供了很好且很友好的API,可以在5分鐘內構建出一個外掛應用。相比較,MEF的易用性也不差,它的程式設計模型也很簡單。次之,Mono.Addins和SharpDevelop的易用性為一般,但MAF和SCSF的易用性最差,應用起來非常複雜。
輔助支援是易用性的重要支撐。在輔助支援方面,OSGi.NET框架構建了外掛遷移工具、外掛釋出工具、遠端管理控制檯、專案模板、除錯日誌、外掛倉庫、示例和文件,提供了非常完善的輔助支撐,相比較,SCSF只提供專案開發模板,Mono.Addins只提供外掛打包工具,而其它框架則沒有任何輔助支援。
這些框架對.NET框架版本的支援不完全一致,MAF對.NET框架版本沒有要求,OSGi.NET、MAF、SharpDevelop、SCSF、Mono.Addins要求.NET框架版本為2.0以上,而MEF則要求.NET框架版本為4.0以上。
根據上述比較可以發現,OSGi.NET框架目前是最簡單、最強大、最通用且最易移植的框架,並構建了符合現代複雜大規模軟體的開發方法——基於軟體生產線的組裝式開發。其獨特優勢有:
(1)唯一支援Web開發,並同時支援WPF、WinForm、ASP.NET、ASP.NET MVC等任意.NET應用;
(2)唯一支援執行緒安全,這也是支援Web開發的前提;
(3)唯一支援國際規範;
(4)唯一不使用跨域支援多版本的框架;
(5)唯一具備完善的輔助支援,並構建了完善的軟體生產線平臺。
對外掛框架的介紹,我簡單到這,這是一個免費的框架,可以從http://www.iopenworks.com來下載,有問題可以加入到我們的外掛交流群:121369588。