.NET Conf 2020 - 基於ASP.NET Core構建可熱插拔的插件化系統

文章標題: .NET Conf 2020 - 基於ASP.NET Core構建可熱插拔的插件化系統
作者: Lamond Lu
項目地址:https://github.com/lamondlu/CoolCat
博客:http://www.cnblogs.com/lwqlun

以下是2020.12.19日的演講文稿和視頻:

大家好,我是陸楠,我來自北京盛安德科技發展有限公司青島分公司,很高興能參加本次.NET開發者大會,今天我分享的主題是《基於ASP.NET Core構建可熱插拔的插件化系統》。

插件化架構,又稱微核架構,指的是軟件的內核相對較小,主要功能和業務邏輯都通過插件實現的架構。

插件化架構一般有兩個核心概念:

  • 內核
  • 插件

內核通常只包含系統運行的最小功能,以及定義插件必須符合的接口;插件則是互相獨立的模塊,一般只包含單一的功能。

插件化技術並不是一個新興的技術,早期很多基於COM開發的WIN32程序其實都是插件化的系統。在.NET/.NET Core中,也有許多插件化的實現方案,例如,開源框架ABP, 開源的內容管理系統DotNetNuke, 電子商務框架NopCommerce。

在設計插件化方案的時候,我們需要考慮一下幾個問題:

  • 如何隔離插件
  • 如何實現插件之間的通訊
  • 如何實現熱插拔

在.NET Framework時代,我們最常用的方案是使用AppDomain應用程序域來封裝插件。使用AppDomain, 我們可以將不同的插件隔離在不同的應用程序域中。至於插件與插件之間的通訊,我們可以藉助MarshalByRefObject類來實現。至於熱插拔,我們可以通過AppDomain自帶的Load/Unload方法來完成,非常的簡單。

但是到了.NET Core中,情況大不相同了。主要的原因是.NET Core中已經將AppDomain的概念移除了,那麼我們該如何實現插件化呢?

這裏我們首先要介紹的是ASP.NET Core中新引入的功能AssemblyLoadContext, 簡稱ALC, ALC提供了一個類似AppDomain的隔離區域,你可以通過ALC來加載程序集,每個ALC加載的程序及之間互不干擾。

這裏請注意,正是由於這種設計,如果將一個程序集引入到兩個不同ALC中,運行時會認爲他們是不同的程序集。

除了自定義的ALC, 在每個ASP.NET Core應用啓動的時候,運行時都會創建一個默認的ALC, 這裏我們需要了解自定義ALC和默認ALC的加載順序

  • 當自定義ALC中的某個插件使用某個程序集的時候,會優先查找當前插件所在的自定義的ALC,如果找不到該程序集,則會進一步查找默認ALC, 所以ALC的程序集加載會優先於默認ALC

我們前面說過,不同的ALC應用相同的程序集,運行時會認爲他們是不同的程序集,所以當兩個插件使用相同程序集的,我們最好將這個程序集加載到默認ALC中,否則在插件交互的時候可能會出現類型衝突。

除了AssemblyLoadContext, 爲了實現插件化,微軟在ASP.NET Core中還提供了另外一個高級特性Application Part - 應用組件。

Application Part並不算一個新特性,因爲在它在.NET Core 2.x版本中就已經被引入了,但是可能部分開發人員沒有過或瞭解過它。Application Part爲ASP.NET Core提供了強大的複用能力,使用Application Part, ASP.NET Core可以從程序集中發現控制器、視圖組件、Razor預編譯視圖、Tag Helper等功能,再借助Application Part Manager, 這些已經編譯好的功能組件就可以在其它項目中直接複用了,這就極大提高了ASP.NET Core功能組件的可複用行。

基於AssemblyLoadContextApplication Part, 你就可以輕鬆的實現ASP.NET Core的插件化了,但是如果想要在ASP.NET Core中實現一個可熱插拔的插件化組件系統,我們還需要針對ASP.NET Core解決很多適配性問題,例如:

  • 如果在運行時加載預編譯視圖?
  • 如果在運行時刷新路由和Controler/Action的映射關係?
  • 一個組件如何從另外一個組件拉取數據
  • 一個組件如何向另外一個組件發送消息通知,完成進一步的業務操作

爲了簡化這一部分的複雜度,我搭建了一個開源項目CoolCat, CoolCat默認支持.NET Core 3.1和.NET 5。

CoolCat已經實現了一下的特性

  • 插件的安裝升級
  • 運行時熱插拔插件
  • 插件間通訊
  • 類Swagger的插件文檔
  • 支持容器化

以下是CoolCat的整體架構圖:

這裏主程序爲CoolCat, 所有的插件都通過ALC加載到主程序中,主程序中定義了通知中心、文檔中心、查詢中心

  • 通知中心負責跨插件的消息通知
  • 文檔中心負責生成插件的文檔
  • 查詢中心負責跨插件的數據查詢

並且爲了簡化創建插件項目,我創建了一個CoolCat插件模板,你可以通過dotnet new命令來安裝插件模板,並安裝模板項目。

dotnet new –i `CoolCat`Module
dotnet new `CoolCat`Module –n {projectName}

這裏創建出的插件項目和一個普通MVC項目相差無幾,比較特殊的是項目會自動生成一個plugin.json文件,裏面包含了當前插件的基本信息。


如果這個項目的生成文件打包,那麼它就是一個可移動的插件安裝包了。

下面呢,我們就通過一個簡單的Demo, 給大家演示一下CoolCat項目的基本功能。

這裏我們首先使用dotnet run命令啓動當前CoolCat, 當程序第一次啓動的時候,會自動建表。這裏呢,我使用了FluentMigrator作爲數據庫遷移工具,所以在項目啓動時,可以進行自動的腳本遷移。

項目啓動之後,我們就可以從瀏覽器打開這個項目了。項目的默認界面是安裝界面,我們可以在這個界面上選擇一些預定義的插件,完成安裝

當然也不僅限於此,如果你想自定義一個其他的預安裝插件,你可以將這些插件放置在項目下的PresetModules目錄中。

這裏呢,我們就選擇默認的2個插件,完成安裝。

安裝完成之後,我們就會自動進入CoolCat的主界面。這裏我們可以通過System菜單下的Plugins子菜單來管理插件。

現在呢,我們來模擬一個場景,假設我們當前開發了2個插件,一個是圖書庫存插件,一個是圖書租借插件。租借插件的數據來源是庫存插件。並且當某本圖書從租借插件租出之後,庫存插件中的當前圖書的狀態也應該變爲出庫狀態。

這裏我們首先通過CoolCat來安裝這2個插件。安裝完成之後,我們啓用插件,這裏大家會發現,當我們啓動插件之後,頂部導航欄中會自動出現這個插件的菜單,這說明我們的熱插拔功能正確的引導並加載的插件。

下一步,我們進入庫存插件,添加一本圖書C#, 添加完成之後,我們會發現這本書的默認狀態是入庫狀態。

現在我們打開圖書租借功能,我們會發現入庫狀態的圖書,正確的顯示在了可租借圖書列表界面,這說明我們的跨插件拉取數據成功。

這時候,我們選擇租出這本書,點擊Rent按鈕,操作完成之後,這本書就從可租借列表中消失了。

下面我們回到圖書庫存插件,你會發現圖書庫存的狀態已經變爲出庫,這是說明我們的跨插件消息傳輸成功了,當圖書租出之後,後續的出庫操作自動完成。

至此,我們就完成了這個簡單的Demo

如果大家感興趣的話,可以下載CoolCat項目自行體驗一下,針對整個框架的研究過程和其中遇到的問題,我都寫在了我的博客園站點中,大家可以自行查看。如果遇到問題或者想參與到本項目中,可以給我發郵件,也可以在博客園給我留言。

以上就是我今天分享的全部內容,謝謝大家。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章