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

框架搭建

2 聚合服务

这里我们将聚合服务命名为Domain.Core

和基础服务层一致,我们先通过命令创建单层模板项目Domain.Core,这里我们删除wwwroot、Data、Entities、Localization、ObjectMapping文件夹及其所有子文件,并删除package.json文件和Services文件夹下的Dtos文件夹

在同级目录添加类库Domain.Core.Constracts并删除其中的Class1.cs文件.

这里我们在聚合服务中暂时不需要提供动态客户端代理,暂时不创建.Client项目,如果需要,创建方式和基础服务层相同。

修改Demo.Core.csproj文件如下:

<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.Serilog" Version="5.3.4" />  </ItemGroup>    <ItemGroup>    <PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="6.0.5" />  </ItemGroup>
  <ItemGroup>    <Content Remove="Localization\Core\*.json" />    <EmbeddedResource Include="Localization\Core\*.json" />    <EmbeddedResource Remove="wwwroot\**" />    <Content Remove="wwwroot\**" />    <Content Remove="package.json" />  </ItemGroup>
  <ItemGroup>    <Compile Remove="Logs\**" />    <Content Remove="Logs\**" />    <EmbeddedResource Remove="Logs\**" />    <None Remove="Logs\**" />    <Compile Remove="wwwroot\**" />    <None Remove="wwwroot\**" />  </ItemGroup>    <ItemGroup>    <ProjectReference Include="..\..\..\service\notificationmanager\Demo.NotificationManager.Client\Demo.NotificationManager.Client.csproj" />    <ProjectReference Include="..\Demo.Core.Contracts\Demo.Core.Contracts.csproj" />  </ItemGroup></Project>
修改CoreModule.cs类代码如下:
using Demo.Core.Contracts;using Demo.NotificationManager.Client;using Microsoft.OpenApi.Models;using Volo.Abp;using Volo.Abp.AspNetCore.Mvc;using Volo.Abp.AspNetCore.Serilog;using Volo.Abp.Autofac;using Volo.Abp.Modularity;using Volo.Abp.Swashbuckle;
namespace Demo.Core;
[DependsOn(    // ABP Framework packages    typeof(AbpAspNetCoreMvcModule),    typeof(AbpAutofacModule),    typeof(AbpSwashbuckleModule),    typeof(AbpAspNetCoreSerilogModule),    typeof(CoreContractsModule),    typeof(NotificationManagerClientModule))]public class CoreModule : AbpModule{    #region 私有方法        #region 配置动态WebApi    private void ConfigureAutoApiControllers()    {        Configure<AbpAspNetCoreMvcOptions>(options =>        {            options.ConventionalControllers.Create(typeof(CoreModule).Assembly);        });    }    #endregion
    #region 配置swagger    private void ConfigureSwagger(IServiceCollection services)    {        services.AddAbpSwaggerGen(            options =>            {                options.SwaggerDoc("v1", new OpenApiInfo { Title = "Core API", Version = "v1" });                options.DocInclusionPredicate((docName, description) => true);                options.CustomSchemaIds(type => type.FullName);            }        );    }    #endregion        #endregion        public override void ConfigureServices(ServiceConfigurationContext context)    {        ConfigureSwagger(context.Services);        ConfigureAutoApiControllers();    }        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", "Core API");        });        app.UseAbpSerilogEnrichers();        app.UseConfiguredEndpoints();    }}
修改Program.cs文件内容如下:
using Demo.Core;using Serilog;using Serilog.Events;
Log.Logger = new LoggerConfiguration()#if DEBUG    .MinimumLevel.Debug()#else                .MinimumLevel.Information()#endif    .MinimumLevel.Override("Microsoft", LogEventLevel.Information)    .Enrich.FromLogContext()    .WriteTo.Async(c => c.File("Logs/logs.txt"))#if DEBUG    .WriteTo.Async(c => c.Console())#endif    .CreateLogger();
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseAutofac().UseSerilog();builder.Services.ReplaceConfiguration(builder.Configuration);builder.Services.AddApplication<CoreModule>();
var app = builder.Build();app.InitializeApplication();app.Run();
修改appsettings.json文件如下:
{  "urls": "http://*:6003",  "RemoteServices": {    "NitificationManager": {      "BaseUrl": "http://localhost:5003/"    }  }}
修改Demo.Core.Contracts.csproj文件:
<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>        <TargetFramework>net6.0</TargetFramework>        <ImplicitUsings>enable</ImplicitUsings>        <Nullable>enable</Nullable>    </PropertyGroup>        <ItemGroup>        <PackageReference Include="Volo.Abp.Ddd.Application.Contracts" Version="5.3.4" />    </ItemGroup></Project>
在Demo.Core.Contracts项目中创建CoreContractsModule.cs文件内容如下:
using Volo.Abp.Application;using Volo.Abp.Modularity;
namespace Demo.Core.Contracts;
[DependsOn(    typeof(AbpDddApplicationContractsModule))]public class CoreContractsModule : AbpModule{
}
启动Demo.Core项目可正常运行并显示swagger页面则聚合服务创建成功。

业务代码

1 基础服务

在Demo.NotificationManager项目Entities文件夹中按领域划分子文件夹,这里创建Notifications领域文件夹,并添加实体类Notification如下:

using System.ComponentModel.DataAnnotations;using Volo.Abp.Domain.Entities.Auditing;
namespace Demo.NotificationManager.Entities.Notifications;
/// <summary>/// 通知/// </summary>public class Notification : CreationAuditedEntity<Guid>{    /// <summary>    /// 接受着用户ID    /// </summary>    public Guid ReceiverId { get; set; }
    /// <summary>    /// 标题    /// </summary>    [StringLength(128)]    public string Title { get; set; }        /// <summary>    /// 内容    /// </summary>    public string Content { get; set; }
    /// <summary>    /// 是否已读    /// </summary>    public bool IsRead { get; set; }}
在Demo.NotificationManager项目Data文件夹下的NotificationManagerDbContext类中添加如下属性并引入相应依赖:
public DbSet<Notification> Notifications { get; set; }
通过 dotnet-ef 的 migrations add 命令和 database update 命令创建数据库结构。

这里我们未用到领域服务,如果需要,可在Demo.NotificationManager项目中添加Managers文件夹并按领域存放对应代码。

在Demo.NotificationManager.Contracts项目中添加Services文件夹,DTO和应用服务接口的定义。这里添加子文件夹Notifications并在其中添加Dtos文件夹,用于存放DTO。

我们添加以下两个DTO分别用于添加和查询通知:

using System.ComponentModel.DataAnnotations;using Volo.Abp.Application.Dtos;
namespace Demo.NotificationManager.Contracts.Services.Notifications.Dtos;
public class NotificationCreateDto:EntityDto<Guid>{    /// <summary>    /// 接受着用户ID    /// </summary>    public Guid ReceiverId { get; set; }
    /// <summary>    /// 标题    /// </summary>    [StringLength(128)]    public string Title { get; set; }        /// <summary>    /// 内容    /// </summary>    public string Content { get; set; }
    /// <summary>    /// 是否已读    /// </summary>    public bool IsRead { get; set; }}
using System.ComponentModel.DataAnnotations;using Volo.Abp.Application.Dtos;using Volo.Abp.Auditing;
namespace Demo.NotificationManager.Contracts.Services.Notifications.Dtos;
public class NotificationDto:NotificationCreateDto,ICreationAuditedObject{    /// <summary>    /// 创建时间    /// </summary>    public DateTime CreationTime { get; set; }        /// <summary>    /// 创建者    /// </summary>    public Guid? CreatorId { get; set; }}

添加INotificationAppService应用服务接口,内容如下:

using Demo.NotificationManager.Contracts.Services.Notifications.Dtos;using Volo.Abp.Application.Dtos;using Volo.Abp.Application.Services;
namespace Demo.NotificationManager.Contracts.Services.Notifications;
public interface    INotificationAppService : ICrudAppService<NotificationDto, Guid, PagedResultRequestDto, NotificationCreateDto>{
}
在Demo.NotificationManger中Services文件夹中添加应用服务实现类,这里添加Notifications子文件夹并创建NotificationAppService类如下:
using Demo.Abp.Extension;using Demo.NotificationManager.Contracts.Services.Notifications;using Demo.NotificationManager.Contracts.Services.Notifications.Dtos;using Demo.NotificationManager.Entities.Notifications;using Volo.Abp.Application.Dtos;using Volo.Abp.Domain.Repositories;
namespace Demo.NotificationManager.Services.Notifications;
public class NotificationAppService : CustomCrudAppService<Notification, NotificationDto, Guid,PagedResultRequestDto,NotificationCreateDto>, INotificationAppService{    public NotificationAppService(IRepository<Notification, Guid> repository) : base(repository)    {    }}
此处,我引入了Mapster框架替代原来的AutoMapper框架,所以我们看到CustomCrudAppService类替代了默认增删改查类CrudAppService。

CrudAppService类被存放在Demo.Abp.Extension类库中作为通用基类使用,代码如下:

using Mapster;using Volo.Abp.Application.Dtos;using Volo.Abp.Application.Services;using Volo.Abp.Domain.Entities;using Volo.Abp.Domain.Repositories;
namespace Demo.Abp.Extension;
#region 重载public abstract class CustomCrudAppService<TEntity, TEntityDto, TKey>    : CustomCrudAppService<TEntity, TEntityDto, TKey, PagedAndSortedResultRequestDto>    where TEntity : class, IEntity<TKey>    where TEntityDto : IEntityDto<TKey>{    protected CustomCrudAppService(IRepository<TEntity, TKey> repository)        : base(repository)    {
    }}
public abstract class CustomCrudAppService<TEntity, TEntityDto, TKey, TGetListInput>    : CustomCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TEntityDto>    where TEntity : class, IEntity<TKey>    where TEntityDto : IEntityDto<TKey>{    protected CustomCrudAppService(IRepository<TEntity, TKey> repository)        : base(repository)    {
    }}
public abstract class CustomCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput>    : CustomCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput, TCreateInput>    where TEntity : class, IEntity<TKey>    where TEntityDto : IEntityDto<TKey>{    protected CustomCrudAppService(IRepository<TEntity, TKey> repository)        : base(repository)    {
    }}
public abstract class CustomCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>    : CustomCrudAppService<TEntity, TEntityDto, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>    where TEntity : class, IEntity<TKey>    where TEntityDto : IEntityDto<TKey>{    protected CustomCrudAppService(IRepository<TEntity, TKey> repository)        : base(repository)    {
    }
    protected override Task<TEntityDto> MapToGetListOutputDtoAsync(TEntity entity)    {        return MapToGetOutputDtoAsync(entity);    }
    protected override TEntityDto MapToGetListOutputDto(TEntity entity)    {        return MapToGetOutputDto(entity);    }}#endregion
public class CustomCrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput,        TUpdateInput>    : CrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>    where TEntity : class, IEntity<TKey>    where TGetOutputDto : IEntityDto<TKey>    where TGetListOutputDto : IEntityDto<TKey>{    public CustomCrudAppService(IRepository<TEntity, TKey> repository) : base(repository)    {    }        #region Mapster    protected override TEntity MapToEntity(TCreateInput createInput)    {        var entity = createInput.Adapt<TEntity>();        SetIdForGuids(entity);        return entity;    }
    protected override void MapToEntity(TUpdateInput updateInput, TEntity entity)    {        if (updateInput is IEntityDto<TKey> entityDto)        {            entityDto.Id = entity.Id;        }        entity = updateInput.Adapt(entity);    }
    protected override TGetOutputDto MapToGetOutputDto(TEntity entity)    {        return entity.Adapt<TGetOutputDto>(); //ObjectMapper.Map<TEntity, TGetOutputDto>(entity);    }    protected override TGetListOutputDto MapToGetListOutputDto(TEntity entity)    {        return entity.Adapt<TGetListOutputDto>(); //ObjectMapper.Map<TEntity, TGetListOutputDto>(entity);    }    #endregion}
此类我继承自原有的CrudAppService类,并重写了对象转换方法。

使用Mapster,比AutoMapper效率更高,而且不需要提前声明默认的对象映射关系,如果需要额外定义映射关系或操作,可通过添加MapConfig类实现。具体用法参考其官方文档:https://github.com/rivenfx/Mapster-docs

如果我们需要定义领域通用配置、枚举等原属于Domain.Share项目中的内容,可创建并存放在Demo.NotificationManager.Contracts项目下Share文件夹中。

完成以上步骤,并可成功运行Demo.NotificationManager项目,则基础服务代码运行成功。