赞
踩
简介
在之前的《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="..\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;
- using Volo.Abp.VirtualFileSystem;
-
-
- namespace Demo.NotificationManager;
-
-
- [DependsOn(
- // ABP Framework packages
- typeof(AbpAspNetCoreMvcModule),
- typeof(AbpAutofacModule),
- typeof(AbpEntityFrameworkCoreMySQLModule),
- typeof(AbpSwashbuckleModule),
- typeof(AbpAspNetCoreSerilogModule),
- typeof(NotificationManagerContractsModule)
- )]
- public class NotificationManagerModule : AbpModule
- {
- #region 私有方法
-
- #region 配置虚拟文件
- private void ConfigureVirtualFiles(IWebHostEnvironment hostingEnvironment)
- {
- Configure<AbpVirtualFileSystemOptions>(options =>
- {
- options.FileSets.AddEmbedded<NotificationManagerModule>();
- if (hostingEnvironment.IsDevelopment())
- {
- /* Using physical files in development, so we don't need to recompile on changes */
- options.FileSets.ReplaceEmbeddedByPhysical<NotificationManagerModule>(hostingEnvironment.ContentRootPath);
- }
- });
- }
- #endregion
-
-
- #region 配置动态webapi
- private void ConfigureAutoApiControllers()
- {
- Configure<AbpAspNetCoreMvcOptions>(options =>
- {
- options.ConventionalControllers.Create(typeof(NotificationManagerModule).Assembly);
- });
- }
- #endregion
-
-
- #region 配置swagger
- private void ConfigureSwagger(IServiceCollection services)
- {
- services.AddAbpSwaggerGen(
- options =>
- {
- options.SwaggerDoc("v1", new OpenApiInfo { Title = "NotificationManager API", Version = "v1" });
- options.DocInclusionPredicate((docName, description) => true);
- options.CustomSchemaIds(type => type.FullName);
- }
- );
- }
- #endregion
-
- #region 设置EF
- private 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
-
-
- #endregion
- public override void ConfigureServices(ServiceConfigurationContext context)
- {
- var hostingEnvironment = context.Services.GetHostingEnvironment();
- var configuration = context.Services.GetConfiguration();
- ConfigureSwagger(context.Services);
- ConfigureAutoApiControllers();
- ConfigureVirtualFiles(hostingEnvironment);
- ConfigureEfCore(context);
- }
-
- public override void OnApplicationInitialization(ApplicationInitializationContext context)
- {
- var app = context.GetApplicationBuilder();
- var env = context.GetEnvironment();
-
-
- if (env.IsDevelopment())
- {
- app.UseDeveloperExceptionPage();
- }
- app.UseAbpRequestLocalization();
- app.UseCorrelationId();
- app.UseStaticFiles();
- app.UseRouting();
- app.UseCors();
- app.UseUnitOfWork();
- app.UseSwagger();
- app.UseAbpSwaggerUI(options =>
- {
- options.SwaggerEndpoint("/swagger/v1/swagger.json", "NotificationManager API");
- });
-
-
- app.UseAuditing();
- app.UseAbpSerilogEnrichers();
- app.UseConfiguredEndpoints();
- }
- }
在主服务中的appsettings.json中删除额外配置项保留如下内容
- {
- "ConnectionStrings": {
- "Default": "Server=localhost;Port=3306;Database=NotificationManager;Uid=root;Pwd=123456;"
- },
- "urls": "http://*:5005"
- }
删除契约层中的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.Modularity;
- using Volo.Abp.Timing;
- using Volo.Abp.VirtualFileSystem;
-
-
- namespace Demo.NotificationManager.Client;
-
-
- 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:5005,可出现Swagger页面则基础服务配置成功。
未完待续
关注我获得
更多精彩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。