.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功能组件的可复用行。
基于AssemblyLoadContext
和Application 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 CoolCatModule
dotnet new CoolCatModule –n {projectName}
这里创建出的插件项目和一个普通MVC项目相差无几,比较特殊的是项目会自动生成一个plugin.json
文件,里面包含了当前插件的基本信息。
如果这个项目的生成文件打包,那么它就是一个可移动的插件安装包了。
下面呢,我们就通过一个简单的Demo, 给大家演示一下CoolCat
项目的基本功能。
这里我们首先使用dotnet run
命令启动当前CoolCat
, 当程序第一次启动的时候,会自动建表。这里呢,我使用了FluentMigrator
作为数据库迁移工具,所以在项目启动时,可以进行自动的脚本迁移。
项目启动之后,我们就可以从浏览器打开这个项目了。项目的默认界面是安装界面,我们可以在这个界面上选择一些预定义的插件,完成安装
当然也不仅限于此,如果你想自定义一个其他的预安装插件,你可以将这些插件放置在项目下的PresetModules
目录中。
这里呢,我们就选择默认的2个插件,完成安装。
安装完成之后,我们就会自动进入CoolCat
的主界面。这里我们可以通过System
菜单下的Plugins
子菜单来管理插件。
现在呢,我们来模拟一个场景,假设我们当前开发了2个插件,一个是图书库存插件,一个是图书租借插件。租借插件的数据来源是库存插件。并且当某本图书从租借插件租出之后,库存插件中的当前图书的状态也应该变为出库状态。
这里我们首先通过CoolCat
来安装这2个插件。安装完成之后,我们启用插件,这里大家会发现,当我们启动插件之后,顶部导航栏中会自动出现这个插件的菜单,这说明我们的热插拔功能正确的引导并加载的插件。
下一步,我们进入库存插件,添加一本图书C#, 添加完成之后,我们会发现这本书的默认状态是入库状态。
现在我们打开图书租借功能,我们会发现入库状态的图书,正确的显示在了可租借图书列表界面,这说明我们的跨插件拉取数据成功。
这时候,我们选择租出这本书,点击Rent按钮,操作完成之后,这本书就从可租借列表中消失了。
下面我们回到图书库存插件,你会发现图书库存的状态已经变为出库,这是说明我们的跨插件消息传输成功了,当图书租出之后,后续的出库操作自动完成。
至此,我们就完成了这个简单的Demo
如果大家感兴趣的话,可以下载CoolCat
项目自行体验一下,针对整个框架的研究过程和其中遇到的问题,我都写在了我的博客园站点中,大家可以自行查看。如果遇到问题或者想参与到本项目中,可以给我发邮件,也可以在博客园给我留言。
以上就是我今天分享的全部内容,谢谢大家。