簡介
在之前的《ABP vNext微服務架構詳細教程》系列中,我們已經構建了完整的微服務架構實例,但是在開發過程中,我們會發現每個基礎服務都包含10個類庫,這是給予DDD四層架構下ABP的實現方案,但是實際使用中我們會發現,隨著微服務的增多,類庫數量的確太過龐大了。而當時受到ABP vNext版本的限制,并沒有一個快速生成精簡應用框架的方式。
到了ABP vNext 5.3版本之后,官方添加了新的模板——單層應用模板,用于解決微服務架構單個項目類庫過多的問題,也給了我們可以快速構建精簡的微服務項目的入口。
這一篇,我就基于單層應用模板,對《ABP vNext微服務架構詳細教程》系列原有微服務框架基礎上進行簡化,在ABP vNext單層模板上進一步精簡的同時,提出一套在微服務架構下單層應用的最佳實踐方案。
此篇內容較長,我會分多節呈現,請一定閱讀到最后。
架構介紹
在之前的文章編寫時ABP vNext版本為5.1.1,只有5.3.0之后版本才支持單頁應用,目前最新正式版版本為5.3.4,這里我們單層模板以5.3.4版本為例。
通過ABP CLI命令,我們可以創建一個簡單的單層應用模板項目。這里的單層是針對類庫來說的,也就是只有一層類庫,但是類庫內部依舊包含著DDD下所有的元素,只是按文件夾劃分并且沒有明確的分層界限。
到當前版本為止,ABP通過官方CLI命令創建的項目,是必須包含用戶角色權限信息管理和身份認證服務的項目。可以理解為過去應用模板的單層形式,但實際在微服務架構下,我們需要進行進一步的調整。
對于整個微服務項目的總體架構和服務分層,我們依舊沿用之前《ABP vNext微服務架構詳細教程》中的設計,詳見:https://mp.weixin.qq.com/s/uDGwxbEhBv15RdMlflb7LA
在聚合服務層和基礎服務層業務服務中,我們使用單層模板為基礎構建我們的服務。包含以下內容:
主服務:WebAPI啟動項,也是ABP單層模板下生成的項目,包含過去Domain、Application、EntityFramworkCore、HttpApi、HttpApi.Host項目的內容,
契約層:當我們在聚合服務層依賴基礎服務層時,我們肯定只是希望依賴基礎服務中接口聲明而非實現,所以將過去項目中的Application.Contracts和Domain.Shared兩個類庫中的內容從單層模板主項目抽離出來就是一個必須的工作。在這里,我們將其放在契約層中
動態客戶端代理:在之前的基礎服務中,包含一個特殊的類庫:HttpApi.Client。它是對基礎服務層動態客戶端代理的封裝和配置,它依賴于Application.Contracts項目,在當前服務中,我們依舊希望把它單獨保留下來,以便于聚合服務實現HTTP調用。
這里,基礎服務層需要包含以上三個項目,而聚合服務層目前沒有提供動態客戶端代理的需求,所以可以只包含主服務和契約層。(雖然從技術角度聚合服務中契約層也不是必須單獨拿出來,但是從架構一致性和擴展性角度,我依舊推薦將契約單獨存放)。
聚合服務層和基礎服務層業務服務依賴關系如下圖:
在整個微服務架構中,身份管理基礎服務比較特殊,由于我們的授權中心依賴身份管理服務的EntityFrameworkCore,如果采用單層架構,則發現EntityFrameworkCore項目必須獨立出來,而EntityFrameworkCore依賴于Domain層,則Domain層也需要獨立,此時我們發現這個項目已經違背了單層應用的初衷。所以身份管理的基礎服務我們依舊采用之前的方式來構建。
另外身份認證服務和網關本身就是單類庫項目,也不需要做調整。
框架搭建
1
基礎服務層
基礎服務我們命名為NotificationManager,通過以下ABP CLI命令,我們可以構建基礎服務的主服務,這里我們選擇無UI模板,MySQL數據庫
abp new Demo.NotificationManager -t app-nolayers -u none -dbms mysql
將該服務添加至主解決方案service/notificationmanger解決方案文件夾下,并在同目錄下分別創建契約層類庫?Demo.NotificationManager.Contracts?和動態客戶端代理類庫?Demo.NotificationManager.Client?。創建后結果如下圖:
由于我們沒有使用多語言,所以直接將主項目中Localization文件夾所有內容刪除。
這里我打算使用另一種對象映射框架,所以刪除主項目中的ObjectMapping文件夾,如果準備繼續使用AutoMapper框架則保留該文件夾。
移除主項目中Services文件夾中的Dtos子文件夾,DTO不存放在該項目中而是在契約層。
由于我們這邊不涉及前端,所以刪除wwwroot文件夾和package.json文件。
刪除主服務Data文件夾下的IdentityServerDataSeedContributor.cs文件。
刪除后主服務項目結構如下:
編輯主項目的Demo.NotificationManager.csproj文件,刪除從??<ItemGroup> <PackageReference Include="Volo.Abp.Account.Application"?Version="5.3.4"?/>??到??<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded"?Version="6.0.5"?/> </ItemGroup>?的所有引用及AutoMapper引用,保留如下內容:
<Project Sdk="Microsoft.NET.Sdk.Web"><PropertyGroup><TargetFramework>net6.0</TargetFramework><ImplicitUsings>enable</ImplicitUsings><GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest></PropertyGroup><ItemGroup><PackageReference Include="Serilog.AspNetCore" Version="5.0.0" /><PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" /></ItemGroup><ItemGroup><PackageReference Include="Volo.Abp.AspNetCore.Mvc" Version="5.3.4" /><PackageReference Include="Volo.Abp.Autofac" Version="5.3.4" /><PackageReference Include="Volo.Abp.Swashbuckle" Version="5.3.4" /><PackageReference Include="Volo.Abp.AspNetCore.Authentication.JwtBearer" Version="5.3.4" /><PackageReference Include="Volo.Abp.AspNetCore.Serilog" Version="5.3.4" /><PackageReference Include="Volo.Abp.EntityFrameworkCore.MySQL" Version="5.3.4" /></ItemGroup><ItemGroup><PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="6.0.5" /></ItemGroup><ItemGroup><PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.5"><IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets><PrivateAssets>compile; contentFiles; build; buildMultitargeting; buildTransitive; analyzers; native</PrivateAssets></PackageReference></ItemGroup><ItemGroup><Compile Remove="Logs\**" /><Content Remove="Logs\**" /><EmbeddedResource Remove="Logs\**" /><None Remove="Logs\**" /></ItemGroup><ItemGroup><ProjectReference Include="..\..\..\common\Demo.Abp.Extension\Demo.Abp.Extension.csproj" /><ProjectReference Include="..\Demo.NotificationManager.Contracts\Demo.NotificationManager.Contracts.csproj" /></ItemGroup>
</Project>
刪除主服務Data文件夾下NotificationManagerDbContext類中所有報錯的行,保留如下內容:
using Demo.NotificationManager.Entities.Notifications;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;namespace Demo.NotificationManager.Data;public class NotificationManagerDbContext : AbpDbContext<NotificationManagerDbContext>
{ public NotificationManagerDbContext(DbContextOptions<NotificationManagerDbContext> options): base(options){}protected override void OnModelCreating(ModelBuilder builder){base.OnModelCreating(builder);}
}
修改Data文件夾下NotificationManagerDbMigrationService類改為以下代碼(這里因為我們沒使用多租戶和初始化數據,所以我移除了相關內容):
using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;namespace Demo.NotificationManager.Data;public class NotificationManagerDbMigrationService : ITransientDependency
{public ILogger<NotificationManagerDbMigrationService> Logger { get; set; }private readonly IDataSeeder _dataSeeder;private readonly NotificationManagerEFCoreDbSchemaMigrator _dbSchemaMigrator;private readonly ICurrentTenant _currentTenant;public NotificationManagerDbMigrationService(IDataSeeder dataSeeder,NotificationManagerEFCoreDbSchemaMigrator dbSchemaMigrator,ICurrentTenant currentTenant){_dataSeeder = dataSeeder;_dbSchemaMigrator = dbSchemaMigrator;_currentTenant = currentTenant;Logger = NullLogger<NotificationManagerDbMigrationService>.Instance;}public async Task MigrateAsync(){var initialMigrationAdded = AddInitialMigrationIfNotExist();if (initialMigrationAdded){return;}Logger.LogInformation("Started database migrations...");await MigrateDatabaseSchemaAsync();Logger.LogInformation("Successfully completed all database migrations.");Logger.LogInformation("You can safely end this process...");}private async Task MigrateDatabaseSchemaAsync(){await _dbSchemaMigrator.MigrateAsync();}private bool AddInitialMigrationIfNotExist(){try{if (!DbMigrationsProjectExists()){return false;}}catch (Exception){return false;}try{if (!MigrationsFolderExists()){AddInitialMigration();return true;}else{return false;}}catch (Exception e){Logger.LogWarning("Couldn't determinate if any migrations exist : " + e.Message);return false;}}private bool DbMigrationsProjectExists(){return Directory.Exists(GetEntityFrameworkCoreProjectFolderPath());}private bool MigrationsFolderExists(){var dbMigrationsProjectFolder = GetEntityFrameworkCoreProjectFolderPath();return Directory.Exists(Path.Combine(dbMigrationsProjectFolder, "Migrations"));}private void AddInitialMigration(){Logger.LogInformation("Creating initial migration...");string argumentPrefix;string fileName;if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux)){argumentPrefix = "-c";fileName = "/bin/bash";}else{argumentPrefix = "/C";fileName = "cmd.exe";}var procStartInfo = new ProcessStartInfo(fileName,$"{argumentPrefix} \"abp create-migration-and-run-migrator \"{GetEntityFrameworkCoreProjectFolderPath()}\" --nolayers\"");try{Process.Start(procStartInfo);}catch (Exception){throw new Exception("Couldn't run ABP CLI...");}}private string GetEntityFrameworkCoreProjectFolderPath(){var slnDirectoryPath = GetSolutionDirectoryPath();if (slnDirectoryPath == null){throw new Exception("Solution folder not found!");}return Path.Combine(slnDirectoryPath, "Demo.NotificationManager");}private string GetSolutionDirectoryPath(){var currentDirectory = new DirectoryInfo(Directory.GetCurrentDirectory());while (Directory.GetParent(currentDirectory.FullName) != null){currentDirectory = Directory.GetParent(currentDirectory.FullName);if (Directory.GetFiles(currentDirectory.FullName).FirstOrDefault(f => f.EndsWith(".sln")) != null){return currentDirectory.FullName;}}return null;}
}
將主服務模塊類修改為以下內容:
using Demo.NotificationManager.Contracts;
using Microsoft.OpenApi.Models;
using Demo.NotificationManager.Data;
using Volo.Abp;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Serilog;
using Volo.Abp.Autofac;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.MySQL;
using Volo.Abp.Modularity;
using Volo.Abp.Swashbuckle;namespace Demo.NotificationManager;[DependsOn(// ABP Framework packagestypeof(AbpAspNetCoreMvcModule),typeof(AbpAutofacModule),typeof(AbpEntityFrameworkCoreMySQLModule),typeof(AbpSwashbuckleModule),typeof(AbpAspNetCoreSerilogModule),typeof(NotificationManagerContractsModule)
)]
public class NotificationManagerModule : AbpModule
{#region 私有方法#region 配置動態webapiprivate void ConfigureAutoApiControllers(){Configure<AbpAspNetCoreMvcOptions>(options =>{options.ConventionalControllers.Create(typeof(NotificationManagerModule).Assembly);});}#endregion#region 配置swaggerprivate void ConfigureSwagger(IServiceCollection services){services.AddAbpSwaggerGen(options =>{options.SwaggerDoc("v1", new OpenApiInfo { Title = "NotificationManager API", Version = "v1" });options.DocInclusionPredicate((_, _) => true);options.CustomSchemaIds(type => type.FullName);});}#endregion#region 設置EFprivate void ConfigureEfCore(ServiceConfigurationContext context){context.Services.AddAbpDbContext<NotificationManagerDbContext>(options =>{/* You can remove "includeAllEntities: true" to create* default repositories only for aggregate roots* Documentation: https://docs.abp.io/en/abp/latest/Entity-Framework-Core#add-default-repositories*/options.AddDefaultRepositories(includeAllEntities: true);});Configure<AbpDbContextOptions>(options =>{options.Configure(configurationContext =>{configurationContext.UseMySQL();});});}#endregion#endregionpublic override void ConfigureServices(ServiceConfigurationContext context){ConfigureSwagger(context.Services);ConfigureAutoApiControllers();ConfigureEfCore(context);}public override void OnApplicationInitialization(ApplicationInitializationContext context){var app = context.GetApplicationBuilder();app.UseRouting();app.UseUnitOfWork();app.UseSwagger();app.UseSwaggerUI(options =>{options.SwaggerEndpoint("/swagger/v1/swagger.json", "NotificationManager API");});app.UseAbpSerilogEnrichers();app.UseConfiguredEndpoints();}
}
?在主服務中的appsettings.json中刪除額外配置項保留如下內容
{"ConnectionStrings": {"Default": "Server=localhost;Port=3306;Database=NotificationManager;Uid=root;Pwd=123456;"},"urls": "http://*:5003"
}
刪除契約層中的Class1.cs,并添加模塊類NotificationManagerContractsModule如下:
using Volo.Abp.Application;
using Volo.Abp.Modularity;namespace Demo.NotificationManager.Contracts;[DependsOn(typeof(AbpDddApplicationContractsModule)
)]
public class NotificationManagerContractsModule : AbpModule
{}
在契約層添加NotificationManagerRemoteServiceConsts類如下:
namespace Demo.NotificationManager.Contracts;public class NotificationManagerRemoteServiceConsts
{public const string RemoteServiceName = "NitificationManager";public const string ModuleName = "nitificationManager";
}
刪除動態客戶端代理層的Class1.cs文件,添加模塊類NotificationManagerClientModule如下:
using Demo.Abp.Extension;
using Demo.NotificationManager.Contracts;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Http.Client;
using Volo.Abp.Modularity;
using Volo.Abp.Timing;
using Volo.Abp.VirtualFileSystem;namespace Demo.NotificationManager.Client;[DependsOn(typeof(NotificationManagerContractsModule), typeof(AbpHttpClientModule))]public class NotificationManagerClientModule : AbpModule
{public override void ConfigureServices(ServiceConfigurationContext context){context.Services.AddTransient<AddHeaderHandler>();context.Services.AddHttpClient(NotificationManagerRemoteServiceConsts.RemoteServiceName).AddHttpMessageHandler<AddHeaderHandler>();context.Services.AddHttpClientProxies(typeof(NotificationManagerContractsModule).Assembly,NotificationManagerRemoteServiceConsts.RemoteServiceName);Configure<AbpVirtualFileSystemOptions>(options =>{options.FileSets.AddEmbedded<NotificationManagerClientModule>();});Configure<AbpClockOptions>(options => { options.Kind = DateTimeKind.Local; });}
}
完成以上修改后,運行項目并用瀏覽器訪問http://localhost:5003,可出現Swagger頁面則基礎服務配置成功。
未完待續
關注我獲得
更多精彩