ABP VNext從單體切換到微服務
阿新 • • 發佈:2020-09-26
注:此處的微服務只考慮服務部分,不考慮內外層閘道器、認證等。
ABP VNext從單體切換到微服務,提供了相當大的便利性,對於各模組內部不要做任何調整,僅需要調整承載體即可。
>ABP can help you in that point by offerring a microservice-compatible, strict module architecture where your module is splitted into multiple layers/projects and developed in its own VS solution completely isolated and independent from other modules. Such a developed module is a natural microservice yet it can be easily plugged-in a monolithic application.
## 分層架構
1. ABP VNext自身提供的分層方式如下圖,核心部分是Application、Domain與EntityFrameworkCore層,至於HttpApi.Client與HttpApi都是屬於將應用去承載到其他系統之上的。
![圖片](https://img2020.cnblogs.com/blog/1133736/202009/1133736-20200926180416644-1696712522.png)
2. Controller層在ABP VNext中被削弱了,VNext提供了將Application提供動態Controller,所以可能習慣了將部分邏輯寫在控制器中的會有些唐突,但理解就行。
![圖片](https://img2020.cnblogs.com/blog/1133736/202009/1133736-20200926180435211-1926539778.png)
## 規劃層次結構
1. 新建空資料夾,取名隨意(本次取名GravelService),用於存放整體專案。
2. 在上一步的空資料夾內新建空白解決方案,可以用VS去生成或是按照dotnet的命令生成。
![圖片](https://img2020.cnblogs.com/blog/1133736/202009/1133736-20200926180453860-163331940.png)
3. 習慣上按照ABP VNext給定的微服務Demo中的格式建立三個資料夾,思路清晰。當然提供的微服務Demo還有更多資料夾,但是這次只關注這三個。
![圖片](https://img2020.cnblogs.com/blog/1133736/202009/1133736-20200926180508021-1246318781.png)
## 增加三個模組
1. 設計三個限界上下文並繪製上下文對映。依照常見的訂單、產品與客戶劃分成三個限界上下文。並規劃好訂單作為下游依賴產品與客戶作為的上游。
![圖片](https://img2020.cnblogs.com/blog/1133736/202009/1133736-20200926180535281-661742588.png)
2. 從官網直接下載三個模組,或是按照命令列去生成(前提是已經安裝了ABP CLI),無論哪種方式,確保準備好就行,依照模組化思想,將其存到modules資料夾下。
![圖片](https://img2020.cnblogs.com/blog/1133736/202009/1133736-20200926180548480-1782631023.png)
3. 如本次準備了三個模組Product,Order,Customer,依照實際需要,可以去精簡一下內部檔案,此處各模組內部我只留著src和test資料夾。
![圖片](https://img2020.cnblogs.com/blog/1133736/202009/1133736-20200926180558763-1250559949.png)
4. 對於customer模組的src內部,具體生成的資料夾如下,對於其中的HttpApi、HttpApi.Client及MongoDB,我採取了剔除,無需這部分(在稍後的承載體中有同等功能)。
![圖片](https://img2020.cnblogs.com/blog/1133736/202009/1133736-20200926180608869-1372734180.png)
清理之後變得簡簡單單的。
![圖片](https://img2020.cnblogs.com/blog/1133736/202009/1133736-20200926180624271-2139456280.png)
對於test內部,同樣採取剔除無需的HttpApi.CLient、MongoDB資料夾(注:此處的無需只是針對我而言,如果有需要還是留著吧)。
![圖片](https://img2020.cnblogs.com/blog/1133736/202009/1133736-20200926180637824-1164479065.png)
清理之後,層次劃分變得簡簡單單的。
![圖片](https://img2020.cnblogs.com/blog/1133736/202009/1133736-20200926180649551-227532524.png)
5. 改造下各模組內給定的Sample案例,改造成各模組命名開頭,方便加以區分。以Customer模組為例,將自帶的Sample案例更名成Customer,僅作該項改動。
![圖片](https://img2020.cnblogs.com/blog/1133736/202009/1133736-20200926180707806-1455384560.png)
## 模擬服務呼叫
1. 對於Order與Product模組內增加一個下游訪問上游服務的鏈路,方便驗證從單體跨越到微服務,底層服務的無需任何改動。對於下游訪問上游服務的呼叫形式,瞭解到的有兩種方式。
* 當前上下文的應用層直接依賴其他上下文的應用服務;
* 當前上下文設定出防腐層(在限界上下文間增加一層隔離,方便保證依賴限界上下文存在變動時只需更改隔離層),在防腐層的實現中去依賴或遠端呼叫其他上下文的應用服務;
2. 我個人傾向於增加防腐層,雖然在VNext中並無相關說法。具體使用時,在領域層或應用層增加防腐層介面,及在EF Core層給與防腐層實現,此時的EF Core層,更應該更名為基礎設施層,而不單單是作為資源庫的形式。
![圖片](https://img2020.cnblogs.com/blog/1133736/202009/1133736-20200926180747366-1071687989.png)
3. 在Order中Domain層增加ServiceClients資料夾用於存放防腐層介面,而在EntityFrameworkCore層增加ServiceClients資料夾用於存放防腐層實現。
* 在防腐層介面中增加IProductServiceClient。
```c#
public interface IProductServiceClient : ITransientDependency
{
Task GetProductId();
}
```
* 在防腐層實現中增加ProductServiceClient。
* 在Order中的EntityFrameworkCore層引用Product模組內的Application.Contracts層。
* 依賴ProductAppService並完成呼叫。
```c#
public class ProductServiceClient : IProductServiceClient
{
private readonly IProductAppService _productAppService;
public ProductServiceClient(IProductAppService productAppService)
{
_productAppService = productAppService;
}
public async Task GetProductId()
{
var productDto = await _productAppService.GetAsync();
return productDto.Value;
}
}
```
* 在OrderAppService中依賴防腐層介面,並完成呼叫。
```c#
public class OrderAppService : OrderManagementAppService, IOrderAppService
{
private readonly IProductServiceClient _productServiceClient;
public OrderAppService(IProductServiceClient productServiceClient)
{
_productServiceClient = productServiceClient;
}
public async Task GetAsync()
{
var productId = await _productServiceClient.GetProductId();
...
}
}
```
4. 這樣一來,在防腐層實現中便可以使用到Product上下文的應用服務了,對於這個應用服務的請求是當前程序內的還是程序間的,由具體的承載方式而決定。
![圖片](https://img2020.cnblogs.com/blog/1133736/202009/1133736-20200926180801896-490606246.png)
## 大單體承載
將依照如下圖,將三個模組承載到GravelService.Host上。
![圖片](https://img2020.cnblogs.com/blog/1133736/202009/1133736-20200926180941011-1193988020.png)
1. 新建一個GravelService.Host的專案,採用WebApi型別即可。存放到microservices資料夾下,這樣方便理解整個層級劃分。
![圖片](https://img2020.cnblogs.com/blog/1133736/202009/1133736-20200926180842735-15671752.png)
2. 其次依賴一些基本的Nuget包,諸如ABP自身的及方便展示Api的Swagger。本次不考慮實體的建立,也就不考慮EF Core包的依賴了。
* Volo.Abp
* Volo.Abp.AspNetCore.MultiTenancy
* Volo.Abp.AspNetCore.Mvc
* Volo.Abp.Autofac
* Swashbuckle.AspNetCore
3. 引用各模組內的Application層及EntityFrameworkCore層。模仿著給定的微服務Demo,完成一波CV操作。增加一個類出來承擔著服務與中介軟體的配置。
![圖片](https://img2020.cnblogs.com/blog/1133736/202009/1133736-20200926180923204-675901469.png)
4. 在其中依賴引用的各Application層及EntityFrameworkCore層中的Module,這樣一來模組依賴就搞定了。
```c#
[DependsOn(
typeof(AbpAutofacModule),
typeof(AbpAspNetCoreMvcModule),
typeof(AbpAspNetCoreMultiTenancyModule),
typeof(CustomerManagementApplicationModule),
typeof(CustomerManagementEntityFrameworkCoreModule),
typeof(OrderManagementApplicationModule),
typeof(OrderManagementEntityFrameworkCoreModule),
typeof(ProductManagementApplicationModule),
typeof(ProductManagementEntityFrameworkCoreModule)
)]
public class GravelServiceHostModule : AbpModule
{
...
}
```
5. 增加服務配置,將各模組中的應用服務動態轉換成Api控制器。
```c#
Configure(options =>
{
options.ConventionalControllers
.Create(typeof(CustomerManagementApplicationModule).Assembly,
opts =>
{
opts.RootPath = "CustomerManagement";
})
.Create(typeof(OrderManagementApplicationModule).Assembly,
opts =>
{
opts.RootPath = "OrderManagement";
})
.Create(typeof(ProductManagementApplicationModule).Assembly,
opts =>
{
opts.RootPath = "ProductManagement";
});
});
```
6. 然後,啟動即可,所有模組中的介面都承載到了該承載體中。
![圖片](https://img2020.cnblogs.com/blog/1133736/202009/1133736-20200926181000907-1878323277.png)
7. 通過訪問Order的api/OrderManagement/order介面獲得返回的9527,也就驗證了,防腐層內去訪問Product上下文的應用服務。
![圖片](https://img2020.cnblogs.com/blog/1133736/202009/1133736-20200926181047439-142609684.png)
此時是在**同一程序內**,使用到的是ProductAppService的具體實現,斷點檢視也可看出當前程序內的請求,依賴其他上下文服務的應用服務為ApplicationServiceProxy。
![圖片](https://img2020.cnblogs.com/blog/1133736/202009/1133736-20200926181101219-202151943.png)
## 多服務承載
依照如下圖開始切換承載模式,只更改承載體,將一個大的承載體切分成各上下文獨自承載體。
![圖片](https://img2020.cnblogs.com/blog/1133736/202009/1133736-20200926181116511-975554570.png)
1. 新建三個和GravelService.Host同等的WebApi專案。在GravelService.Host上裁剪即可,每個獨立的承載體只擁有自身的上下文。
![圖片](https://img2020.cnblogs.com/blog/1133736/202009/1133736-20200926181127757-254463246.png)
2. 依據上下文對映關係,對處於下游的Order承載體增加對上游Product的依賴,在Order承載體中增加對Product模組中Application.Contracts層的依賴。並在服務配置中完成依賴(第7行)。
```c#
[DependsOn(
typeof(AbpAutofacModule),
typeof(AbpAspNetCoreMvcModule),
typeof(AbpAspNetCoreMultiTenancyModule),
typeof(OrderManagementApplicationModule),
typeof(OrderManagementEntityFrameworkCoreModule),
typeof(ProductManagementApplicationContractsModule)
)]
public class OrderServiceHostModule : AbpModule
{
...
}
```
3. 配置遠端服務代理,在服務配置中設定從Order承載體到Product承載體的遠端服務請求。在Order承載體中增加Volo.Abp.Http.Client包並在服務配置中完成依賴。
```c#
[DependsOn(
...
typeof(AbpHttpClientModule),
...
)]
public class OrderServiceHostModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
...
context.Services.AddHttpClientProxies( typeof(ProductManagementApplicationContractsModule).Assembly);
...
}
}
```
4. 在配置源appsettings檔案中加入對Product承載體的遠端呼叫地址
```plain
{
"RemoteServices": {
"Default": {
"BaseUrl": "http://localhost:57687"
}
}
```
5. 更改專案配置,啟動多個檔案,一併啟動三個獨立承載體,再次呼叫Order中的Get請求,同樣獲得了從Product上下文的應用服務中的資料
![圖片](https://img2020.cnblogs.com/blog/1133736/202009/1133736-20200926181141013-303145734.png)
6. 這次採用的**不同程序間**的承載方式,通過防腐層,然後經ABP提供的Http代理服務呼叫Product承載體,可以斷點檢視當前在防腐層中的AppService型別,已經不再是直接使用ProductAppService了。
![圖片](https://img2020.cnblogs.com/blog/1133736/202009/1133736-20200926181153724-334993010.png)
## 真香
從大單體承載切換多服務承載,Modules部分不需要做任何更改,著實方便。至於內部的遠端呼叫,本身ABP VNext還存在一些問題像類似集合的Get請求在3.1的版本中才做了修復,但是這絲毫不影響這一巨人的前行。
[碼雲倉庫](https://gitee.com/StarCity/StarCity.Gravel.git "碼雲倉庫")
>2020-09-26,望技術有成後能回來看見自己的腳步