当前位置:   article > 正文

ABP vNext微服务架构详细教程(补充篇)——单层模板(上)

abp vnext

简介

5f90c7e668f61502a3d78a62b33426f4.png

在之前的《ABP vNext微服务架构详细教程》系列中,我们已经构建了完整的微服务架构实例,但是在开发过程中,我们会发现每个基础服务都包含10个类库,这是给予DDD四层架构下ABP的实现方案,但是实际使用中我们会发现,随着微服务的增多,类库数量的确太过庞大了。而当时受到ABP vNext版本的限制,并没有一个快速生成精简应用框架的方式。

9e297c2308c4bcebec3938ffff1e9129.png

到了ABP vNext 5.3版本之后,官方添加了新的模板——单层应用模板,用于解决微服务架构单个项目类库过多的问题,也给了我们可以快速构建精简的微服务项目的入口。

ec32211d503e52397f2679c505080aa7.png

这一篇,我就基于单层应用模板,对《ABP vNext微服务架构详细教程》系列原有微服务框架基础上进行简化,在ABP vNext单层模板上进一步精简的同时,提出一套在微服务架构下单层应用的最佳实践方案。

4ab60b993fcd209e658a47e5f05044e6.png

此篇内容过长,我会分多节讲述,请一定阅读到最后。

架构介绍

5c117d55f7a7aad2611a02db25813f98.png

在之前的文章编写时ABP vNext版本为5.1.1,只有5.3.0之后版本才支持单页应用,目前最新正式版版本为5.3.4,这里我们单层模板以5.3.4版本为例。

de22363e1a09da0b964dafd41c90f0df.gif

通过ABP CLI命令,我们可以创建一个简单的单层应用模板项目。这里的单层是针对类库来说的,也就是只有一层类库,但是类库内部依旧包含着DDD下所有的元素,只是按文件夹划分并且没有明确的分层界限。

fea6b6430a676a99ec1bd8931e01d65b.gif

到当前版本为止,ABP通过官方CLI命令创建的项目,是必须包含用户角色权限信息管理和身份认证服务的项目。可以理解为过去应用模板的单层形式,但实际在微服务架构下,我们需要进行进一步的调整。

a58655dc5fa5bc6960f6e90865b24468.gif

对于整个微服务项目的总体架构和服务分层,我们依旧沿用之前《ABP vNext微服务架构详细教程》中的设计,详见:https://mp.weixin.qq.com/s/uDGwxbEhBv15RdMlflb7LA

dd7a3aca0859b0c7f36d7f15965111cc.gif

在聚合服务层和基础服务层业务服务中,我们使用单层模板为基础构建我们的服务。包含以下内容:

主服务:WebAPI启动项,也是ABP单层模板下生成的项目,包含过去Domain、Application、EntityFramworkCore、HttpApi、HttpApi.Host项目的内容,

契约层:当我们在聚合服务层依赖基础服务层时,我们肯定只是希望依赖基础服务中接口声明而非实现,所以将过去项目中的Application.Contracts和Domain.Shared两个类库中的内容从单层模板主项目抽离出来就是一个必须的工作。在这里,我们将其放在契约层中

动态客户端代理:在之前的基础服务中,包含一个特殊的类库:HttpApi.Client。它是对基础服务层动态客户端代理的封装和配置,它依赖于Application.Contracts项目,在当前服务中,我们依旧希望把它单独保留下来,以便于聚合服务实现HTTP调用。

6185959e6b4b11cada38e6163ecfb9e0.png

这里,基础服务层需要包含以上三个项目,而聚合服务层目前没有提供动态客户端代理的需求,所以可以只包含主服务和契约层。(虽然从技术角度聚合服务中契约层也不是必须单独拿出来,但是从架构一致性和扩展性角度,我依旧推荐将契约单独存放)。

9f64e1cf6edbf10caa8c5346ac501a6c.gif

聚合服务层和基础服务层业务服务依赖关系如下图:

770a1f0ae61b3af8ed09a36e8b5e8c6f.png

e2f54394547111d2c4dcc4192342f377.png

在整个微服务架构中,身份管理基础服务比较特殊,由于我们的授权中心依赖身份管理服务的EntityFrameworkCore,如果采用单层架构,则发现EntityFrameworkCore项目必须独立出来,而EntityFrameworkCore依赖于Domain层,则Domain层也需要独立,此时我们发现这个项目已经违背了单层应用的初衷。所以身份管理的基础服务我们依旧采用之前的方式来构建。

另外身份认证服务和网关本身就是单类库项目,也不需要做调整。

框架搭建

1

基础服务层

59322391316bc3a7c34c35f1dca4f27b.gif

基础服务我们命名为NotificationManager,通过以下ABP CLI命令,我们可以构建基础服务的主服务,这里我们选择无UI模板,MySQL数据库

abp new Demo.NotificationManager -t app-nolayers -u none -dbms mysql

将该服务添加至主解决方案service/notificationmanger解决方案文件夹下,并在同目录下分别创建契约层类库 Demo.NotificationManager.Contracts 和动态客户端代理类库 Demo.NotificationManager.Client 。创建后结果如下图:

ad054d6f255a0459d694b63fd5e34f6a.png

0ec9ac326b409acdb3c2beaeebb8dd12.gif

由于我们没有使用多语言,所以直接将主项目中Localization文件夹所有内容删除。

这里我打算使用另一种对象映射框架,所以删除主项目中的ObjectMapping文件夹,如果准备继续使用AutoMapper框架则保留该文件夹。

移除主项目中Services文件夹中的Dtos子文件夹,DTO不存放在该项目中而是在契约层。

由于我们这边不涉及前端,所以删除wwwroot文件夹和package.json文件。

删除主服务Data文件夹下的IdentityServerDataSeedContributor.cs文件。

删除后主服务项目结构如下:

dffda5af894260e43b90309089178571.png

40110a84b71498287d265f42414d444f.gif

编辑主项目的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引用,保留如下内容:

  1. <Project Sdk="Microsoft.NET.Sdk.Web">
  2. <PropertyGroup>
  3. <TargetFramework>net6.0</TargetFramework>
  4. <ImplicitUsings>enable</ImplicitUsings>
  5. <GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
  6. </PropertyGroup>
  7. <ItemGroup>
  8. <PackageReference Include="Serilog.AspNetCore" Version="5.0.0" />
  9. <PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
  10. </ItemGroup>
  11. <ItemGroup>
  12. <PackageReference Include="Volo.Abp.AspNetCore.Mvc" Version="5.3.4" />
  13. <PackageReference Include="Volo.Abp.Autofac" Version="5.3.4" />
  14. <PackageReference Include="Volo.Abp.Swashbuckle" Version="5.3.4" />
  15. <PackageReference Include="Volo.Abp.AspNetCore.Authentication.JwtBearer" Version="5.3.4" />
  16. <PackageReference Include="Volo.Abp.AspNetCore.Serilog" Version="5.3.4" />
  17. <PackageReference Include="Volo.Abp.EntityFrameworkCore.MySQL" Version="5.3.4" />
  18. </ItemGroup>
  19. <ItemGroup>
  20. <PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="6.0.5" />
  21. </ItemGroup>
  22. <ItemGroup>
  23. <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.5">
  24. <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
  25. <PrivateAssets>compile; contentFiles; build; buildMultitargeting; buildTransitive; analyzers; native</PrivateAssets>
  26. </PackageReference>
  27. </ItemGroup>
  28. <ItemGroup>
  29. <Compile Remove="Logs\**" />
  30. <Content Remove="Logs\**" />
  31. <EmbeddedResource Remove="Logs\**" />
  32. <None Remove="Logs\**" />
  33. </ItemGroup>
  34. <ItemGroup>
  35. <ProjectReference Include="..\Demo.NotificationManager.Contracts\Demo.NotificationManager.Contracts.csproj" />
  36. </ItemGroup>
  37. </Project>

a8d410c27065d7627477f47f6801873b.gif

删除主服务Data文件夹下NotificationManagerDbContext类中所有报错的行,保留如下内容:

  1. using Demo.NotificationManager.Entities.Notifications;
  2. using Microsoft.EntityFrameworkCore;
  3. using Volo.Abp.EntityFrameworkCore;
  4. namespace Demo.NotificationManager.Data;
  5. public class NotificationManagerDbContext : AbpDbContext<NotificationManagerDbContext>
  6. {
  7. public NotificationManagerDbContext(DbContextOptions<NotificationManagerDbContext> options)
  8. : base(options)
  9. {
  10. }
  11. protected override void OnModelCreating(ModelBuilder builder)
  12. {
  13. base.OnModelCreating(builder);
  14. }
  15. }

62935db577087c96c184ec8fbfae78c4.gif

修改Data文件夹下NotificationManagerDbMigrationService类改为以下代码(这里因为我们没使用多租户和初始化数据,所以我移除了相关内容):

  1. using System.Diagnostics;
  2. using System.Runtime.InteropServices;
  3. using Microsoft.Extensions.Logging.Abstractions;
  4. using Volo.Abp.Data;
  5. using Volo.Abp.DependencyInjection;
  6. using Volo.Abp.MultiTenancy;
  7. namespace Demo.NotificationManager.Data;
  8. public class NotificationManagerDbMigrationService : ITransientDependency
  9. {
  10. public ILogger<NotificationManagerDbMigrationService> Logger { get; set; }
  11. private readonly IDataSeeder _dataSeeder;
  12. private readonly NotificationManagerEFCoreDbSchemaMigrator _dbSchemaMigrator;
  13. private readonly ICurrentTenant _currentTenant;
  14. public NotificationManagerDbMigrationService(
  15. IDataSeeder dataSeeder,
  16. NotificationManagerEFCoreDbSchemaMigrator dbSchemaMigrator,
  17. ICurrentTenant currentTenant)
  18. {
  19. _dataSeeder = dataSeeder;
  20. _dbSchemaMigrator = dbSchemaMigrator;
  21. _currentTenant = currentTenant;
  22. Logger = NullLogger<NotificationManagerDbMigrationService>.Instance;
  23. }
  24. public async Task MigrateAsync()
  25. {
  26. var initialMigrationAdded = AddInitialMigrationIfNotExist();
  27. if (initialMigrationAdded)
  28. {
  29. return;
  30. }
  31. Logger.LogInformation("Started database migrations...");
  32. await MigrateDatabaseSchemaAsync();
  33. Logger.LogInformation("Successfully completed all database migrations.");
  34. Logger.LogInformation("You can safely end this process...");
  35. }
  36. private async Task MigrateDatabaseSchemaAsync()
  37. {
  38. await _dbSchemaMigrator.MigrateAsync();
  39. }
  40. private bool AddInitialMigrationIfNotExist()
  41. {
  42. try
  43. {
  44. if (!DbMigrationsProjectExists())
  45. {
  46. return false;
  47. }
  48. }
  49. catch (Exception)
  50. {
  51. return false;
  52. }
  53. try
  54. {
  55. if (!MigrationsFolderExists())
  56. {
  57. AddInitialMigration();
  58. return true;
  59. }
  60. else
  61. {
  62. return false;
  63. }
  64. }
  65. catch (Exception e)
  66. {
  67. Logger.LogWarning("Couldn't determinate if any migrations exist : " + e.Message);
  68. return false;
  69. }
  70. }
  71. private bool DbMigrationsProjectExists()
  72. {
  73. return Directory.Exists(GetEntityFrameworkCoreProjectFolderPath());
  74. }
  75. private bool MigrationsFolderExists()
  76. {
  77. var dbMigrationsProjectFolder = GetEntityFrameworkCoreProjectFolderPath();
  78. return Directory.Exists(Path.Combine(dbMigrationsProjectFolder, "Migrations"));
  79. }
  80. private void AddInitialMigration()
  81. {
  82. Logger.LogInformation("Creating initial migration...");
  83. string argumentPrefix;
  84. string fileName;
  85. if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
  86. {
  87. argumentPrefix = "-c";
  88. fileName = "/bin/bash";
  89. }
  90. else
  91. {
  92. argumentPrefix = "/C";
  93. fileName = "cmd.exe";
  94. }
  95. var procStartInfo = new ProcessStartInfo(fileName,
  96. $"{argumentPrefix} \"abp create-migration-and-run-migrator \"{GetEntityFrameworkCoreProjectFolderPath()}\" --nolayers\""
  97. );
  98. try
  99. {
  100. Process.Start(procStartInfo);
  101. }
  102. catch (Exception)
  103. {
  104. throw new Exception("Couldn't run ABP CLI...");
  105. }
  106. }
  107. private string GetEntityFrameworkCoreProjectFolderPath()
  108. {
  109. var slnDirectoryPath = GetSolutionDirectoryPath();
  110. if (slnDirectoryPath == null)
  111. {
  112. throw new Exception("Solution folder not found!");
  113. }
  114. return Path.Combine(slnDirectoryPath, "Demo.NotificationManager");
  115. }
  116. private string GetSolutionDirectoryPath()
  117. {
  118. var currentDirectory = new DirectoryInfo(Directory.GetCurrentDirectory());
  119. while (Directory.GetParent(currentDirectory.FullName) != null)
  120. {
  121. currentDirectory = Directory.GetParent(currentDirectory.FullName);
  122. if (Directory.GetFiles(currentDirectory.FullName).FirstOrDefault(f => f.EndsWith(".sln")) != null)
  123. {
  124. return currentDirectory.FullName;
  125. }
  126. }
  127. return null;
  128. }
  129. }

c63105d006a8cbaf16bb2589f592892c.gif

将主服务模块类修改为以下内容:

  1. using Demo.NotificationManager.Contracts;
  2. using Microsoft.OpenApi.Models;
  3. using Demo.NotificationManager.Data;
  4. using Volo.Abp;
  5. using Volo.Abp.AspNetCore.Mvc;
  6. using Volo.Abp.AspNetCore.Serilog;
  7. using Volo.Abp.Autofac;
  8. using Volo.Abp.EntityFrameworkCore;
  9. using Volo.Abp.EntityFrameworkCore.MySQL;
  10. using Volo.Abp.Modularity;
  11. using Volo.Abp.Swashbuckle;
  12. using Volo.Abp.VirtualFileSystem;
  13. namespace Demo.NotificationManager;
  14. [DependsOn(
  15. // ABP Framework packages
  16. typeof(AbpAspNetCoreMvcModule),
  17. typeof(AbpAutofacModule),
  18. typeof(AbpEntityFrameworkCoreMySQLModule),
  19. typeof(AbpSwashbuckleModule),
  20. typeof(AbpAspNetCoreSerilogModule),
  21. typeof(NotificationManagerContractsModule)
  22. )]
  23. public class NotificationManagerModule : AbpModule
  24. {
  25. #region 私有方法
  26. #region 配置虚拟文件
  27. private void ConfigureVirtualFiles(IWebHostEnvironment hostingEnvironment)
  28. {
  29. Configure<AbpVirtualFileSystemOptions>(options =>
  30. {
  31. options.FileSets.AddEmbedded<NotificationManagerModule>();
  32. if (hostingEnvironment.IsDevelopment())
  33. {
  34. /* Using physical files in development, so we don't need to recompile on changes */
  35. options.FileSets.ReplaceEmbeddedByPhysical<NotificationManagerModule>(hostingEnvironment.ContentRootPath);
  36. }
  37. });
  38. }
  39. #endregion
  40. #region 配置动态webapi
  41. private void ConfigureAutoApiControllers()
  42. {
  43. Configure<AbpAspNetCoreMvcOptions>(options =>
  44. {
  45. options.ConventionalControllers.Create(typeof(NotificationManagerModule).Assembly);
  46. });
  47. }
  48. #endregion
  49. #region 配置swagger
  50. private void ConfigureSwagger(IServiceCollection services)
  51. {
  52. services.AddAbpSwaggerGen(
  53. options =>
  54. {
  55. options.SwaggerDoc("v1", new OpenApiInfo { Title = "NotificationManager API", Version = "v1" });
  56. options.DocInclusionPredicate((docName, description) => true);
  57. options.CustomSchemaIds(type => type.FullName);
  58. }
  59. );
  60. }
  61. #endregion
  62. #region 设置EF
  63. private void ConfigureEfCore(ServiceConfigurationContext context)
  64. {
  65. context.Services.AddAbpDbContext<NotificationManagerDbContext>(options =>
  66. {
  67. /* You can remove "includeAllEntities: true" to create
  68. * default repositories only for aggregate roots
  69. * Documentation: https://docs.abp.io/en/abp/latest/Entity-Framework-Core#add-default-repositories
  70. */
  71. options.AddDefaultRepositories(includeAllEntities: true);
  72. });
  73. Configure<AbpDbContextOptions>(options =>
  74. {
  75. options.Configure(configurationContext =>
  76. {
  77. configurationContext.UseMySQL();
  78. });
  79. });
  80. }
  81. #endregion
  82. #endregion
  83. public override void ConfigureServices(ServiceConfigurationContext context)
  84. {
  85. var hostingEnvironment = context.Services.GetHostingEnvironment();
  86. var configuration = context.Services.GetConfiguration();
  87. ConfigureSwagger(context.Services);
  88. ConfigureAutoApiControllers();
  89. ConfigureVirtualFiles(hostingEnvironment);
  90. ConfigureEfCore(context);
  91. }
  92. public override void OnApplicationInitialization(ApplicationInitializationContext context)
  93. {
  94. var app = context.GetApplicationBuilder();
  95. var env = context.GetEnvironment();
  96. if (env.IsDevelopment())
  97. {
  98. app.UseDeveloperExceptionPage();
  99. }
  100. app.UseAbpRequestLocalization();
  101. app.UseCorrelationId();
  102. app.UseStaticFiles();
  103. app.UseRouting();
  104. app.UseCors();
  105. app.UseUnitOfWork();
  106. app.UseSwagger();
  107. app.UseAbpSwaggerUI(options =>
  108. {
  109. options.SwaggerEndpoint("/swagger/v1/swagger.json", "NotificationManager API");
  110. });
  111. app.UseAuditing();
  112. app.UseAbpSerilogEnrichers();
  113. app.UseConfiguredEndpoints();
  114. }
  115. }

d8b1b065cef583b1cd8f3f135c910a92.gif

 在主服务中的appsettings.json中删除额外配置项保留如下内容

  1. {
  2. "ConnectionStrings": {
  3. "Default": "Server=localhost;Port=3306;Database=NotificationManager;Uid=root;Pwd=123456;"
  4. },
  5. "urls": "http://*:5005"
  6. }

0e72133ff3dcd5a4cff160b7484b9dc1.gif

删除契约层中的Class1.cs,并添加模块类NotificationManagerContractsModule如下:

  1. using Volo.Abp.Application;
  2. using Volo.Abp.Modularity;
  3. namespace Demo.NotificationManager.Contracts;
  4. [DependsOn(
  5. typeof(AbpDddApplicationContractsModule)
  6. )]
  7. public class NotificationManagerContractsModule : AbpModule
  8. {
  9. }

523313abd1943b96c19d55b5a6694aa3.gif

在契约层添加NotificationManagerRemoteServiceConsts类如下:

  1. namespace Demo.NotificationManager.Contracts;
  2. public class NotificationManagerRemoteServiceConsts
  3. {
  4. public const string RemoteServiceName = "NitificationManager";
  5. public const string ModuleName = "nitificationManager";
  6. }

df2330d1ffa18553a30d118ab749cd54.gif

删除动态客户端代理层的Class1.cs文件,添加模块类NotificationManagerClientModule如下:

  1. using Demo.Abp.Extension;
  2. using Demo.NotificationManager.Contracts;
  3. using Microsoft.Extensions.DependencyInjection;
  4. using Volo.Abp.Modularity;
  5. using Volo.Abp.Timing;
  6. using Volo.Abp.VirtualFileSystem;
  7. namespace Demo.NotificationManager.Client;
  8. public class NotificationManagerClientModule : AbpModule
  9. {
  10. public override void ConfigureServices(ServiceConfigurationContext context)
  11. {
  12. context.Services.AddTransient<AddHeaderHandler>();
  13. context.Services.AddHttpClient(NotificationManagerRemoteServiceConsts.RemoteServiceName)
  14. .AddHttpMessageHandler<AddHeaderHandler>();
  15. context.Services.AddHttpClientProxies(
  16. typeof(NotificationManagerContractsModule).Assembly,
  17. NotificationManagerRemoteServiceConsts.RemoteServiceName
  18. );
  19. Configure<AbpVirtualFileSystemOptions>(options =>
  20. {
  21. options.FileSets.AddEmbedded<NotificationManagerClientModule>();
  22. });
  23. Configure<AbpClockOptions>(options => { options.Kind = DateTimeKind.Local; });
  24. }
  25. }

6504ed25bcddeee8a826204e8d359ca5.png

完成以上修改后,运行项目并用浏览器访问http://localhost:5005,可出现Swagger页面则基础服务配置成功。

4ef5e79417ab3cfacc214af428e27c2c.png

未完待续

5fb175abbec9e8be36dca4ae00b2925d.jpeg

8d37c40a695ce2c2e63617c829716baa.png

关注我获得

更多精彩

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/141385
推荐阅读
相关标签
  

闽ICP备14008679号