源宝导读:微软跨平台技术框架—.NET Core已经日趋成熟,已经具备了支撑大型系统稳定运行的条件。本文将介绍明源云ERP平台从.NET Framework向.NET Core迁移过程中的实践经验。
一、背景
随着ERP的产品线越来越多,业务关联也日益复杂,应用间依赖关系也变得错综复杂,单体架构的弱点日趋明显。19年初,平台底层支持了分应用部署模式,将ERP从应用子系统层面进行了切割分离,迈出了从单体架构向微服务架构转型的坚实一步。不久的将来,ERP会进一步将各业务拆分成众多的微服务,而微服务势必需要进行容器化部署和运行管理,这就要求ERP技术底层必须支持跨平台,所以将现有ERP系统从.NET Framework迁移到 .NET Core平台势在必行。
二、遇到的问题和应对策略
ERP在从Framework迁移到Core的时候主要要解决以下问题:
代码文件和工程迁移。
Aspx文件迁移到Razor。
HttpModule和HttpHandler迁移。
ClownFish的Mvc机制迁移到Asp.Net Core MVC。
其他一些Windows平台Api迁移到跨平台。
发布和Docker部署。
以上每个主题中都包含了大量的技术细节,我们后面将陆续发文章分享经验。本文先介绍关于项目工程迁移方面的内容,具体的迁移步骤如下:
dll引用实际是一个层级关系,从启动项目往下找是可以找到一个最底层的依赖的,然后会存在一个依赖顺序(拓扑排序),我们改造的时候也是从最底层开始改造起,逐个工程这样迁移,迁移过程保证编译通过。
在类库迁移的时候核心有三个点,cs文件迁移,新的工程(.csproj)文件组织,dll引用组织。
在站点项目迁移的时候需要考虑静态资源(js,css等)的组织。
在做完这些之后要考虑dll版本和最终打包发布。
三、迁移代码文件
.NET Framework 的 .csproj 文件极其庞大且难以理解,在新的.Net Core中推出了全新的的.csproj组织方式。新的工程文件主要有如下优势:
在版本管理中更容易解决冲突:新方式包含了目录下所有文件,老方式是需要显示的引用(有点像黑名单和白名单的意思),这样声明的文件变得更少,从而减少了多人协作时XML合并冲突的风险。
可以指定多个开发框架,提供更好的兼容性:以往如果要同时兼容.Net3.5和.Net 4.5的话需要建两个工程,通过文件链接来处理多个框架兼容的问题,新的组织方式一个工程文件即可处理。
引用更加简洁:没有对其他项目的基于GUID的引用,这可以提高文件的可读性。同时基于NuGet的引用和路径无关,意味着可以指定任意的NuGet包的位置。
嵌套的引用不需要重复指定(如果 A 引用了 B,B 引用了 C;那么 A 不需要显式引用 C 也能调用到 C)。
项目迁移就是要实现对原来.cs代码进行迁移,实现逻辑的复用,这里有三种迁移代码文件的方式:
1、Copy文件: 将原有的文件复制复制到新文件夹重新组织(基本相当于重新写一套就不赘述了)。
2、文件链接: 使用文件链接只需新建一个工程使用来组织代码,基于Framework和Core可以分别再额外添加文件,下面是示例:
3、多框架:一个工程文件中有多个框架,下面是示例:
我们的策略是,根据不同的情况使用不同的迁移方式:
对于基础平台,由于是ERP的基础底座,必须保证其迁移后的稳定和兼容,但基础平台的代码量大,功能多,如果仅靠大量测试很难保证整体迁移的质量。由于Core和Framework的Api的一些差异,并不是完全兼容,但微软还是提供了最大限度地功能兼容。为了以最低成本保障迁移质量,最终我们采用的是文件链接的迁移方式。
对于平台的文档服务和配置中心,功能较简单,全面进行回归测试的成本不大,所以我们采用了多框架的迁移方式。虽然.NET Core在HTTP框架方面并不兼容,我们需要重写个别项目,这些项目的代码量不大,所以对迁移质量的影响不大。
对于平台的调度服务,我们采用重写加上多框架的迁移方式。只是因为调度服务的核心引擎比较简单,我们直接采用重写的迁移方式,这部分后面将会有单独的文章来介绍。
四、处理代码文件兼容
当我们解决了工程层面的兼容之后,引入的文件编译肯定会报错,解决这个问题这个最核心的其实就是条件编译了,示例如下:
虽然可以使用上述手段适配不兼容的API调用,但如果某个API被用到的地方特别多,就会出现大量重复的条件编译,面对这个问题,我们可以将调用API这部分代码抽离成单独的Helper类。举个例子:在Framework和Core中,从Http上下文中取参数的方法存在很大差异,而之前代码中大量调用了这个API方法,我们可以将调用此API的代码封装到一个单独的Helper类,如下代码所示:
上述示例中有如下几点比较重要:
命名空间中使用重命名的方式提供了参数的兼容使调用方代码统一。
AsBase这类空方法提供了调用代码的统一。
在方法级别使用了条件编译,结合后续的处理逻辑兼容提供了调用方代码统一。
在方法内部使用了条件编译,直接提供了方法调用的兼容。
在命名空间重命名的技巧中还有另外一个用法也特别有用,还是举例说明:
对比上述代码可以发现,使用占位Attribute的方式代码会简洁很多,还有类似很多技巧这里就不一一说明。
改造过程中总结出以下实践:
先用最简单的条件编译。
一个类里面方法和参数的特性导致重复的条件编译比较多,考虑在命名空间部分使用重命名。
一个类里面方法内部重复的条件编译比较多,考虑用类中私有方法来封装这部分重复逻辑。
多个类里面方法内部重复的条件编译比较多,考虑用用Helper类来封装。
如果是方案级别的不一样,考虑用一个接口在Framework和Core中不同的实现来处理。
五、处理包引用
在处理完代码层面兼容之后,还要处理第三方类库的引用的问题。我们先通过下面两张图看看Framework和Core中引用的不同。
Framework引用关系:
Core引用关系:
在Framework中都是平铺的,而在Core中引用是树形结构;比如在Framework中A引用了B,B引用C,A如果要用到C的功能就必须要显式的引用C,但是在CoreA是不需要引用C的。所以我们改造的时候遵循从底层网上改,如果底层引用的包上层就不再引用,这样大大减少了项目中包引用的数量。新的引用机制也带来了一些问题:
平台周边的类库文件,如果按照原来方式是需要打包两个dll,然后手动添加dll引用,这样使仓库体积变大,同时也不容易维护版本。
由于.Net Core的模块化更细,一个包可能依赖于很多的其他包,这样就导致了拉取nuget包很慢。
基于上述问题我们引入了Nexus作为我们的包管理工具。通过这个工具可以将自己的类库推送到自己的NuGet仓库管理,它同时还提供了代理的功能可以将远程的包存储到局域网,大大提高了拉包的速度。可能有些同学没有使用过Nexus,这里做一个简单的介绍:
功能: 提供的包类型非常丰富,支持nuget,maven,docker,npm,pypl,yum等,几乎覆盖市面所有流行的语言和工具。
Host仓库: 支持自建仓库可以上传自己制作的包并共享出来。
Proxy仓库: 支持为其他远程仓库地址代理,通过代理可以将其他网站的软件包缓存到本地,大大提高拉包的效率。
Group仓库: 支持分组将多个代理和私服打包成一个组,这样远程包的地址集中管理。
权限管理:在任意一个仓库都可以控制增删改查等权限。
总之:还有更多功能大家可以去试试(强烈推荐)。
六、处理静态文件复用
我们解决了类库的迁移之后,由于在.NetCore改造过程中完全不涉及到前端js,css等文件的改造,所以要保证的js和css可以复用。在Framework中所有的资源文件都是相对于站点根目录的路径,而在Core中所有的资源文件都是在根目录的wwwroot中,而在Core中提供了WebRootPath可以指定资源文件的路径,我们在启动过程中做了如下配置:
.Net Core中可以根据环境变量来加载不同的配置文件,首先会默认加载appsettings.json,在开发环境中会额外的加载 appsettings.Development.json文件,后添加文件的配置项会覆盖先添加文件的相同配置项,这样在生产环境中使用默认的WebRootPath配置,而在开发环境中把WebRootPath指向原来Framework站点路径,即可实现开发环境和生产环境的资源文件复用。
七、处理dll版本迭代
在ERP发布过程中是需要管理版本的,每个dll的版本都应该保持一致,那么就会涉及到如何通过修改一个地方让所有dll版本都升级。
在Framework中我们用到了两种方法:
Assembly文件链接,在周边服务中Assembly中不需要有太多信息,这个时候通过一个只有版本信息的Assembly文件然后通过文件链接方式加入到各个工程中,发布时候只需修改一个Assembly的版本号即可。
老版本target文件引用,在每个工程中引入如下target文件,然后发布时候修改VersionAssembly中版本号即可。
在Core中我们采用引入props文件(和target文件类似) ,使用方式如下所示:
在Core中通过引入props文件这种方式可以将所有工程的通用描述包含进来,我们还用来在此文件中描述了dll文件签名等其他内容。
八、程序打包发布
最后一步就是打包发布了, Framework中发布将编译后的站点目录进行打包,但是在Core中需要调用dotnet命令发布,发布之后还要考虑资源文件,dll版本,使用 dotnet publish {启动项目相对路径} 命令发布即可,在平台服务中我们还使用了将独立发布的模式。
因为ERP是前后端在一个仓库,而且有众多的资源文件,这里给出发布的脚本仅供大家参考:
九、总结
在改造过程中由于要兼容老的Framework的功能,给整个过程带来了很多需要兼容的问题。初看之下可能会很乱,但是在一步步分析之后还是有思路可以做,并且在改造过程中大量采用了条件编译延伸出来的技巧,使改造过程的思路和方法越来越清晰,最终完成了整个改造专项。
在整个过程中也遇到代码不兼容的场景,这个时候就考虑从功能层面来兼容,比如:授权和权限,页面路由,资源文件合并和替换等等,这些具体到功能细节改造的部分,将会在后面文章中陆续进行介绍,敬请期待。
------ END ------
作者简介
熊同学: 研发工程师,目前负责ERP平台相关的设计与开发工作。
也许您还想看
.NET Core MVC扩展实践
研发协同平台架构演进
明源云助手产品日志服务的演化历程
ERP缓存实践经验分享