当前位置:   article > 正文

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

abpvnext

简介

33ea2c9ebb58cd0d9fb761ae97f0261d.png

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

071b35be0f4372b01dc6451d37ff8c46.png

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

a610d9eb7995dd8b6c32885381168ff4.png

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

753ad0d70aff9d3420b901725e9b7037.png

此篇内容较长,我会分多节呈现,请一定阅读到最后。

架构介绍

693f2f6d0cea327dbbd56b2d5f8d65b6.png

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

cfcfcd8eaf865afa1938cf3424329c31.gif

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

6d65ef0c850731d5c84bbd2884d1bb66.gif

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

5544b7f56401b0dbf74e606e0f05db60.gif

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

73814f4291c4a408ffa2b32ba39962de.gif

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

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

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

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

6087b6239cdeacbdbb0f0fa4ce7b50c8.png

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

2405921d9ad48b71af800c6d6d3e61e8.gif

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

a04ee0cef1e44ec2f1cdb2e98eb3e4c0.png

2a99cfccd3952dbfdcf90e9c3d50f87d.png

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

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

框架搭建

1

基础服务层

fe6ba3cd3f46670b0ee07a85705a6c35.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 。创建后结果如下图:

3cc9707e8286b9ab0d8fbbf9c336f120.png

99e234115cc327a6e87d9415a262d684.gif

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

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

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

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

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

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

ca1e3adbdb199e44381a18bc4e56bcc9.png

53d13d97fc04ee646f393b1f5ab84c6d.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="..\..\..\common\Demo.Abp.Extension\Demo.Abp.Extension.csproj" />
  36. <ProjectReference Include="..\Demo.NotificationManager.Contracts\Demo.NotificationManager.Contracts.csproj" />
  37. </ItemGroup>
  38. </Project>

47fc7b07c7e5d94d21a781ccc776d95b.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. }

44319a0af541c910ad7fc08b2bd03a0b.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. }

9372b59b69fbb293d814e27673b5174b.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. namespace Demo.NotificationManager;
  13. [DependsOn(
  14. // ABP Framework packages
  15. typeof(AbpAspNetCoreMvcModule),
  16. typeof(AbpAutofacModule),
  17. typeof(AbpEntityFrameworkCoreMySQLModule),
  18. typeof(AbpSwashbuckleModule),
  19. typeof(AbpAspNetCoreSerilogModule),
  20. typeof(NotificationManagerContractsModule)
  21. )]
  22. public class NotificationManagerModule : AbpModule
  23. {
  24. #region 私有方法
  25. #region 配置动态webapi
  26. private void ConfigureAutoApiControllers()
  27. {
  28. Configure<AbpAspNetCoreMvcOptions>(options =>
  29. {
  30. options.ConventionalControllers.Create(typeof(NotificationManagerModule).Assembly);
  31. });
  32. }
  33. #endregion
  34. #region 配置swagger
  35. private void ConfigureSwagger(IServiceCollection services)
  36. {
  37. services.AddAbpSwaggerGen(
  38. options =>
  39. {
  40. options.SwaggerDoc("v1", new OpenApiInfo { Title = "NotificationManager API", Version = "v1" });
  41. options.DocInclusionPredicate((_, _) => true);
  42. options.CustomSchemaIds(type => type.FullName);
  43. }
  44. );
  45. }
  46. #endregion
  47. #region 设置EF
  48. private void ConfigureEfCore(ServiceConfigurationContext context)
  49. {
  50. context.Services.AddAbpDbContext<NotificationManagerDbContext>(options =>
  51. {
  52. /* You can remove "includeAllEntities: true" to create
  53. * default repositories only for aggregate roots
  54. * Documentation: https://docs.abp.io/en/abp/latest/Entity-Framework-Core#add-default-repositories
  55. */
  56. options.AddDefaultRepositories(includeAllEntities: true);
  57. });
  58. Configure<AbpDbContextOptions>(options =>
  59. {
  60. options.Configure(configurationContext =>
  61. {
  62. configurationContext.UseMySQL();
  63. });
  64. });
  65. }
  66. #endregion
  67. #endregion
  68. public override void ConfigureServices(ServiceConfigurationContext context)
  69. {
  70. ConfigureSwagger(context.Services);
  71. ConfigureAutoApiControllers();
  72. ConfigureEfCore(context);
  73. }
  74. public override void OnApplicationInitialization(ApplicationInitializationContext context)
  75. {
  76. var app = context.GetApplicationBuilder();
  77. app.UseRouting();
  78. app.UseUnitOfWork();
  79. app.UseSwagger();
  80. app.UseSwaggerUI(options =>
  81. {
  82. options.SwaggerEndpoint("/swagger/v1/swagger.json", "NotificationManager API");
  83. });
  84. app.UseAbpSerilogEnrichers();
  85. app.UseConfiguredEndpoints();
  86. }
  87. }

6a9c9c430455653ce50a454a43fed814.gif

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

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

fe6c706faa223dee90e14d6c1e9ec751.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. }

0e056abbc85002ac2c4ee7d8a2c1f7c2.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. }

9181cebcb468d8a9b47021c31cacdae6.gif

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

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

8c79174e863c26c9104c9cc515702cb8.png

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

d33d01dedc7d7b8d6940bb03eae905d2.png

未完待续

087391b1b35c83a31a05f40292cde3a6.jpeg

79ed1d02b74fa4d5cc6d3314088b3d39.png

关注我获得

更多精彩

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

闽ICP备14008679号