【复杂系统迁移 .NET Core平台系列】之迁移项目工程
源寶導(dǎo)讀:微軟跨平臺(tái)技術(shù)框架—.NET Core已經(jīng)日趨成熟,已經(jīng)具備了支撐大型系統(tǒng)穩(wěn)定運(yùn)行的條件。本文將介紹明源云ERP平臺(tái)從.NET Framework向.NET Core遷移過(guò)程中的實(shí)踐經(jīng)驗(yàn)。
一、背景
? ??隨著ERP的產(chǎn)品線越來(lái)越多,業(yè)務(wù)關(guān)聯(lián)也日益復(fù)雜,應(yīng)用間依賴關(guān)系也變得錯(cuò)綜復(fù)雜,單體架構(gòu)的弱點(diǎn)日趨明顯。19年初,平臺(tái)底層支持了分應(yīng)用部署模式,將ERP從應(yīng)用子系統(tǒng)層面進(jìn)行了切割分離,邁出了從單體架構(gòu)向微服務(wù)架構(gòu)轉(zhuǎn)型的堅(jiān)實(shí)一步。不久的將來(lái),ERP會(huì)進(jìn)一步將各業(yè)務(wù)拆分成眾多的微服務(wù),而微服務(wù)勢(shì)必需要進(jìn)行容器化部署和運(yùn)行管理,這就要求ERP技術(shù)底層必須支持跨平臺(tái),所以將現(xiàn)有ERP系統(tǒng)從.NET Framework遷移到 .NET Core平臺(tái)勢(shì)在必行。
二、遇到的問(wèn)題和應(yīng)對(duì)策略
? ? ERP在從Framework遷移到Core的時(shí)候主要要解決以下問(wèn)題:
代碼文件和工程遷移。
Aspx文件遷移到Razor。
HttpModule和HttpHandler遷移。
ClownFish的Mvc機(jī)制遷移到Asp.Net Core MVC。
其他一些Windows平臺(tái)Api遷移到跨平臺(tái)。
發(fā)布和Docker部署。
? ? 以上每個(gè)主題中都包含了大量的技術(shù)細(xì)節(jié),我們后面將陸續(xù)發(fā)文章分享經(jīng)驗(yàn)。本文先介紹關(guān)于項(xiàng)目工程遷移方面的內(nèi)容,具體的遷移步驟如下:
dll引用實(shí)際是一個(gè)層級(jí)關(guān)系,從啟動(dòng)項(xiàng)目往下找是可以找到一個(gè)最底層的依賴的,然后會(huì)存在一個(gè)依賴順序(拓?fù)渑判?,我們改造的時(shí)候也是從最底層開始改造起,逐個(gè)工程這樣遷移,遷移過(guò)程保證編譯通過(guò)。
在類庫(kù)遷移的時(shí)候核心有三個(gè)點(diǎn),cs文件遷移,新的工程(.csproj)文件組織,dll引用組織。
在站點(diǎn)項(xiàng)目遷移的時(shí)候需要考慮靜態(tài)資源(js,css等)的組織。
在做完這些之后要考慮dll版本和最終打包發(fā)布。
三、遷移代碼文件
? ? .NET Framework 的 .csproj 文件極其龐大且難以理解,在新的.Net Core中推出了全新的的.csproj組織方式。新的工程文件主要有如下優(yōu)勢(shì):
在版本管理中更容易解決沖突:新方式包含了目錄下所有文件,老方式是需要顯示的引用(有點(diǎn)像黑名單和白名單的意思),這樣聲明的文件變得更少,從而減少了多人協(xié)作時(shí)XML合并沖突的風(fēng)險(xiǎn)。
可以指定多個(gè)開發(fā)框架,提供更好的兼容性:以往如果要同時(shí)兼容.Net3.5和.Net 4.5的話需要建兩個(gè)工程,通過(guò)文件鏈接來(lái)處理多個(gè)框架兼容的問(wèn)題,新的組織方式一個(gè)工程文件即可處理。
引用更加簡(jiǎn)潔:沒(méi)有對(duì)其他項(xiàng)目的基于GUID的引用,這可以提高文件的可讀性。同時(shí)基于NuGet的引用和路徑無(wú)關(guān),意味著可以指定任意的NuGet包的位置。
嵌套的引用不需要重復(fù)指定(如果 A 引用了 B,B 引用了 C;那么 A 不需要顯式引用 C 也能調(diào)用到 C)。
? ? 項(xiàng)目遷移就是要實(shí)現(xiàn)對(duì)原來(lái).cs代碼進(jìn)行遷移,實(shí)現(xiàn)邏輯的復(fù)用,這里有三種遷移代碼文件的方式:
1、Copy文件: 將原有的文件復(fù)制復(fù)制到新文件夾重新組織(基本相當(dāng)于重新寫一套就不贅述了)。
2、文件鏈接: 使用文件鏈接只需新建一個(gè)工程使用來(lái)組織代碼,基于Framework和Core可以分別再額外添加文件,下面是示例:
3、多框架:一個(gè)工程文件中有多個(gè)框架,下面是示例:
? ? 我們的策略是,根據(jù)不同的情況使用不同的遷移方式:
對(duì)于基礎(chǔ)平臺(tái),由于是ERP的基礎(chǔ)底座,必須保證其遷移后的穩(wěn)定和兼容,但基礎(chǔ)平臺(tái)的代碼量大,功能多,如果僅靠大量測(cè)試很難保證整體遷移的質(zhì)量。由于Core和Framework的Api的一些差異,并不是完全兼容,但微軟還是提供了最大限度地功能兼容。為了以最低成本保障遷移質(zhì)量,最終我們采用的是文件鏈接的遷移方式。
對(duì)于平臺(tái)的文檔服務(wù)和配置中心,功能較簡(jiǎn)單,全面進(jìn)行回歸測(cè)試的成本不大,所以我們采用了多框架的遷移方式。雖然.NET Core在HTTP框架方面并不兼容,我們需要重寫個(gè)別項(xiàng)目,這些項(xiàng)目的代碼量不大,所以對(duì)遷移質(zhì)量的影響不大。
對(duì)于平臺(tái)的調(diào)度服務(wù),我們采用重寫加上多框架的遷移方式。只是因?yàn)檎{(diào)度服務(wù)的核心引擎比較簡(jiǎn)單,我們直接采用重寫的遷移方式,這部分后面將會(huì)有單獨(dú)的文章來(lái)介紹。
四、處理代碼文件兼容
? ? 當(dāng)我們解決了工程層面的兼容之后,引入的文件編譯肯定會(huì)報(bào)錯(cuò),解決這個(gè)問(wèn)題這個(gè)最核心的其實(shí)就是條件編譯了,示例如下:
? ? 雖然可以使用上述手段適配不兼容的API調(diào)用,但如果某個(gè)API被用到的地方特別多,就會(huì)出現(xiàn)大量重復(fù)的條件編譯,面對(duì)這個(gè)問(wèn)題,我們可以將調(diào)用API這部分代碼抽離成單獨(dú)的Helper類。舉個(gè)例子:在Framework和Core中,從Http上下文中取參數(shù)的方法存在很大差異,而之前代碼中大量調(diào)用了這個(gè)API方法,我們可以將調(diào)用此API的代碼封裝到一個(gè)單獨(dú)的Helper類,如下代碼所示:
? ? 上述示例中有如下幾點(diǎn)比較重要:
命名空間中使用重命名的方式提供了參數(shù)的兼容使調(diào)用方代碼統(tǒng)一。
AsBase這類空方法提供了調(diào)用代碼的統(tǒng)一。
在方法級(jí)別使用了條件編譯,結(jié)合后續(xù)的處理邏輯兼容提供了調(diào)用方代碼統(tǒng)一。
在方法內(nèi)部使用了條件編譯,直接提供了方法調(diào)用的兼容。
? ? 在命名空間重命名的技巧中還有另外一個(gè)用法也特別有用,還是舉例說(shuō)明:
? ? 對(duì)比上述代碼可以發(fā)現(xiàn),使用占位Attribute的方式代碼會(huì)簡(jiǎn)潔很多,還有類似很多技巧這里就不一一說(shuō)明。
? ? 改造過(guò)程中總結(jié)出以下實(shí)踐:
先用最簡(jiǎn)單的條件編譯。
一個(gè)類里面方法和參數(shù)的特性導(dǎo)致重復(fù)的條件編譯比較多,考慮在命名空間部分使用重命名。
一個(gè)類里面方法內(nèi)部重復(fù)的條件編譯比較多,考慮用類中私有方法來(lái)封裝這部分重復(fù)邏輯。
多個(gè)類里面方法內(nèi)部重復(fù)的條件編譯比較多,考慮用用Helper類來(lái)封裝。
如果是方案級(jí)別的不一樣,考慮用一個(gè)接口在Framework和Core中不同的實(shí)現(xiàn)來(lái)處理。
五、處理包引用
? ??在處理完代碼層面兼容之后,還要處理第三方類庫(kù)的引用的問(wèn)題。我們先通過(guò)下面兩張圖看看Framework和Core中引用的不同。
Framework引用關(guān)系:
Core引用關(guān)系:
? ? 在Framework中都是平鋪的,而在Core中引用是樹形結(jié)構(gòu);比如在Framework中A引用了B,B引用C,A如果要用到C的功能就必須要顯式的引用C,但是在CoreA是不需要引用C的。所以我們改造的時(shí)候遵循從底層網(wǎng)上改,如果底層引用的包上層就不再引用,這樣大大減少了項(xiàng)目中包引用的數(shù)量。新的引用機(jī)制也帶來(lái)了一些問(wèn)題:
平臺(tái)周邊的類庫(kù)文件,如果按照原來(lái)方式是需要打包兩個(gè)dll,然后手動(dòng)添加dll引用,這樣使倉(cāng)庫(kù)體積變大,同時(shí)也不容易維護(hù)版本。
由于.Net Core的模塊化更細(xì),一個(gè)包可能依賴于很多的其他包,這樣就導(dǎo)致了拉取nuget包很慢。
? ? 基于上述問(wèn)題我們引入了Nexus作為我們的包管理工具。通過(guò)這個(gè)工具可以將自己的類庫(kù)推送到自己的NuGet倉(cāng)庫(kù)管理,它同時(shí)還提供了代理的功能可以將遠(yuǎn)程的包存儲(chǔ)到局域網(wǎng),大大提高了拉包的速度。可能有些同學(xué)沒(méi)有使用過(guò)Nexus,這里做一個(gè)簡(jiǎn)單的介紹:
功能: 提供的包類型非常豐富,支持nuget,maven,docker,npm,pypl,yum等,幾乎覆蓋市面所有流行的語(yǔ)言和工具。
Host倉(cāng)庫(kù): 支持自建倉(cāng)庫(kù)可以上傳自己制作的包并共享出來(lái)。
Proxy倉(cāng)庫(kù): 支持為其他遠(yuǎn)程倉(cāng)庫(kù)地址代理,通過(guò)代理可以將其他網(wǎng)站的軟件包緩存到本地,大大提高拉包的效率。
Group倉(cāng)庫(kù): 支持分組將多個(gè)代理和私服打包成一個(gè)組,這樣遠(yuǎn)程包的地址集中管理。
權(quán)限管理:在任意一個(gè)倉(cāng)庫(kù)都可以控制增刪改查等權(quán)限。
總之:還有更多功能大家可以去試試(強(qiáng)烈推薦)。
六、處理靜態(tài)文件復(fù)用
? ??我們解決了類庫(kù)的遷移之后,由于在.NetCore改造過(guò)程中完全不涉及到前端js,css等文件的改造,所以要保證的js和css可以復(fù)用。在Framework中所有的資源文件都是相對(duì)于站點(diǎn)根目錄的路徑,而在Core中所有的資源文件都是在根目錄的wwwroot中,而在Core中提供了WebRootPath可以指定資源文件的路徑,我們?cè)趩?dòng)過(guò)程中做了如下配置:
? ? .Net Core中可以根據(jù)環(huán)境變量來(lái)加載不同的配置文件,首先會(huì)默認(rèn)加載appsettings.json,在開發(fā)環(huán)境中會(huì)額外的加載 appsettings.Development.json文件,后添加文件的配置項(xiàng)會(huì)覆蓋先添加文件的相同配置項(xiàng),這樣在生產(chǎn)環(huán)境中使用默認(rèn)的WebRootPath配置,而在開發(fā)環(huán)境中把WebRootPath指向原來(lái)Framework站點(diǎn)路徑,即可實(shí)現(xiàn)開發(fā)環(huán)境和生產(chǎn)環(huán)境的資源文件復(fù)用。
七、處理dll版本迭代
? ? 在ERP發(fā)布過(guò)程中是需要管理版本的,每個(gè)dll的版本都應(yīng)該保持一致,那么就會(huì)涉及到如何通過(guò)修改一個(gè)地方讓所有dll版本都升級(jí)。
在Framework中我們用到了兩種方法:
Assembly文件鏈接,在周邊服務(wù)中Assembly中不需要有太多信息,這個(gè)時(shí)候通過(guò)一個(gè)只有版本信息的Assembly文件然后通過(guò)文件鏈接方式加入到各個(gè)工程中,發(fā)布時(shí)候只需修改一個(gè)Assembly的版本號(hào)即可。
老版本target文件引用,在每個(gè)工程中引入如下target文件,然后發(fā)布時(shí)候修改VersionAssembly中版本號(hào)即可。
在Core中我們采用引入props文件(和target文件類似) ,使用方式如下所示:
? ? 在Core中通過(guò)引入props文件這種方式可以將所有工程的通用描述包含進(jìn)來(lái),我們還用來(lái)在此文件中描述了dll文件簽名等其他內(nèi)容。
八、程序打包發(fā)布
? ? 最后一步就是打包發(fā)布了, Framework中發(fā)布將編譯后的站點(diǎn)目錄進(jìn)行打包,但是在Core中需要調(diào)用dotnet命令發(fā)布,發(fā)布之后還要考慮資源文件,dll版本,使用 dotnet publish {啟動(dòng)項(xiàng)目相對(duì)路徑}?命令發(fā)布即可,在平臺(tái)服務(wù)中我們還使用了將獨(dú)立發(fā)布的模式。
? ? 因?yàn)镋RP是前后端在一個(gè)倉(cāng)庫(kù),而且有眾多的資源文件,這里給出發(fā)布的腳本僅供大家參考:
九、總結(jié)
? ? 在改造過(guò)程中由于要兼容老的Framework的功能,給整個(gè)過(guò)程帶來(lái)了很多需要兼容的問(wèn)題。初看之下可能會(huì)很亂,但是在一步步分析之后還是有思路可以做,并且在改造過(guò)程中大量采用了條件編譯延伸出來(lái)的技巧,使改造過(guò)程的思路和方法越來(lái)越清晰,最終完成了整個(gè)改造專項(xiàng)。
? ? 在整個(gè)過(guò)程中也遇到代碼不兼容的場(chǎng)景,這個(gè)時(shí)候就考慮從功能層面來(lái)兼容,比如:授權(quán)和權(quán)限,頁(yè)面路由,資源文件合并和替換等等,這些具體到功能細(xì)節(jié)改造的部分,將會(huì)在后面文章中陸續(xù)進(jìn)行介紹,敬請(qǐng)期待。
------ END ------
作者簡(jiǎn)介
熊同學(xué):?研發(fā)工程師,目前負(fù)責(zé)ERP平臺(tái)相關(guān)的設(shè)計(jì)與開發(fā)工作。
也許您還想看
.NET Core MVC擴(kuò)展實(shí)踐
研發(fā)協(xié)同平臺(tái)架構(gòu)演進(jìn)
明源云助手產(chǎn)品日志服務(wù)的演化歷程
ERP緩存實(shí)踐經(jīng)驗(yàn)分享
總結(jié)
以上是生活随笔為你收集整理的【复杂系统迁移 .NET Core平台系列】之迁移项目工程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: WeihanLi.Npoi 1.7.0
- 下一篇: 【复杂系统迁移 .NET Core平台系