ABP vNext微服务架构详细教程——聚合服务


1. 服务创建

聚合服务层是基础服务层的封装及聚合,只需要提供对外WebAPI接口,不需要提供数据库访问和领域对象。所以我们只需要创建Host、Application和Application.Contracts即可。

这里我们以商城服务为例在总解决方案下创建application文件夹用于存放所有层代码,并创建子文件夹store,用于存放商城服务所有代码。

在总项目解决方案文件夹中添加解决方案文件夹与上一步目录结构相同。

在总项目解决方案中添加空白项目Demo.Store.HttpApi.Host和类库项目Demo.Store.Application及Demo.Store.Application.Contracts

添加引用关系:Demo.Store.HttpApi.Host引用Demo.Store.Application,Demo.Store.Application引用Demo.Store.Application.Contracts

添加完成后项目结构如下:

2. 基础配置

为Demo.Store.Application.Contracts项目添加Nuget依赖:Volo.Abp.Ddd.Application.Contracts

为Demo.Store.Application.Contracts项目添加类DemoStoreApplicationContractsModule代码如下:

using Volo.Abp.Application;
using Volo.Abp.Modularity;

namespace Demo.Store.Application.Contracts;

[DependsOn(typeof(AbpDddApplicationContractsModule))]
public class DemoStoreApplicationContractsModule : AbpModule
{

}

为Demo.Store.Application项目添加Nuget依赖:Volo.Abp.Ddd.Application、Volo.Abp.AutoMapper

为Demo.Store.Application项目添加类DemoStoreApplicationModule代码如下:

using Demo.Store.Application.Contracts;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Application;
using Volo.Abp.AutoMapper;
using Volo.Abp.Modularity;

namespace Demo.Store.Application;

[DependsOn(typeof(AbpDddApplicationModule))]
[DependsOn(typeof(DemoStoreApplicationContractsModule))]
[DependsOn(typeof(AbpAutoMapperModule))]
public class DemoStoreApplicationModule:AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        context.Services.AddAutoMapperObjectMapper();
        Configure(options =>
        {
            options.AddMaps(validate: false);
        });
    }
}

为Demo.Store.HttpApi.Host项目添加Nuget依赖:Serilog.AspNetCore、Serilog.Sinks.Async、Volo.Abp.AspNetCore.Mvc、Volo.Abp.Autofac、Volo.Abp.Swashbuckle

为Demo.Store.HttpApi.Host项目添加类DemoStoreHttpApiHostModule代码如下:

using Demo.Store.Application;
using Microsoft.OpenApi.Models;
using Volo.Abp;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Autofac;
using Volo.Abp.Modularity;
using Volo.Abp.Swashbuckle;

namespace Demo.Store.HttpApi.Host;

[DependsOn(typeof(AbpAspNetCoreMvcModule))]
[DependsOn(typeof(AbpAutofacModule))]
[DependsOn(typeof(DemoStoreApplicationModule))]
[DependsOn(typeof(AbpSwashbuckleModule))]
public class DemoStoreHttpApiHostModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        Configure(options =>
        {
            options
                .ConventionalControllers
                .Create(typeof(DemoStoreApplicationModule).Assembly);
        });
        context.Services.AddAbpSwaggerGen(
            options =>
            {
                options.SwaggerDoc("v1", new OpenApiInfo { Title = "Store API", Version = "v1" });
                options.DocInclusionPredicate((docName, description) => true);
                options.CustomSchemaIds(type => type.FullName);
            }
        );
    }

    public override void OnApplicationInitialization(ApplicationInitializationContext context)
    {
        var app = context.GetApplicationBuilder();
        var env = context.GetEnvironment();
        
        if (env.IsDevelopment())
        {
            app.UseExceptionHandler("/Error");
        }
        app.UseSwagger();
        app.UseAbpSwaggerUI(options =>
        {
            options.SwaggerEndpoint("/swagger/v1/swagger.json", "Test API");
        });

        app.UseStaticFiles();
        app.UseRouting();
        app.UseConfiguredEndpoints();
    }
}

修改Demo.Store.HttpApi.Host项目Program类如下:

using Demo.Store.HttpApi.Host;
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();


var app = builder.Build();
app.InitializeApplication();
app.Run();

为Demo.Store.HttpApi.Host项目配置文件appsettings.json增加如下配置

"urls": "http://*:6002"

运行Demo.Store.HttpApi.Host项目并访问http://localhost:6002/swagger/index.html可正常显示Swagger页面即添加成功

3.添加动态客户端代理

ABP vNext框架提供了动态代理机制,详见:https://docs.abp.io/en/abp/latest/API/Dynamic-CSharp-API-Clients

这里我们的我们的商城服务依赖订单管理和商品管理两个微服务

因为ABP框架通过CLI命令拉取的模板已包含API调用的动态客户端代理封装,即HttpAPi.Client项目,我们这里只需要为Demo.Store.Application项目添加Demo.OrderManager.HttpApi.Client和Demo.ProductManager.HttpApi.Client的项目引用并在DemoStoreApplicationModule类中添加DependOn特性如下:

[DependsOn(typeof(OrderManagerHttpApiClientModule),typeof(ProductManagerHttpApiClientModule))]

之后我们需要在Demo.Store.HttpApi.Host项目的配置文件appsettings.json中添加两个服务的地址配置如下:

"RemoteServices": {
  "ProductManager": {
    "BaseUrl": "http://localhost:5010/"
  },
  "OrderManager": {
    "BaseUrl": "http://localhost:5011/"
  }
}

 4.接口开发

在Demo.Store.Application.Contracts项目下添加Store文件夹并在其下添加Dto子文件夹

在Dto文件夹下添加数据传输对象,这里以添加订单服务接口为例,依次添加StoreProductCountDto与StoreOrderCreateDto类如下:

using System.ComponentModel.DataAnnotations;

namespace Demo.Store.Application.Contracts.Store.Dto;
/// 
/// 产品数量DTO
/// 
public record StoreProductCountDto
{
    /// 
    /// 产品编号
    /// 
    public Guid ProductId { get; set; }

    /// 
    /// 数量
    /// 
    [Range(1, 100000)]
    public int Count { get; set; }
}
namespace Demo.Store.Application.Contracts.Store.Dto;

/// 
/// 创建订单DTO
/// 
public class StoreOrderCreateDto
{
    /// 
    /// 参考号
    /// 
    public string ReferenceNo { get; init; }

    /// 
    /// 订单商品列表
    /// 
    public List Products { get; init; }
}

在Demo.Store.Application.Contracts项目Store文件夹下添加应用服务接口IStoreOrderAppService如下:

using Volo.Abp.Application.Services;

namespace Demo.Store.Application.Contracts.Store;

/// 
/// 商城订单应用服务接口
/// 
public interface IStoreOrderAppService : IApplicationService
{
    /// 
    /// 创建订单
    /// 
    /// 输入对象
    /// 创建结果
    Task<bool> CreateAsync(StoreOrderCreateDto input);
}

在Demo.Store.Application项目添加Store文件夹

在Demo.Store.Application项目Store文件夹中添加StoreAutoMapperProfile类用于存放该领域映射关系如下:

using Demo.OrderManager.Orders.Dto;
using Demo.ProductManager.Products.Dto;
using Demo.Store.Application.Contracts.Store.Dto;
using Volo.Abp.AutoMapper;

namespace Demo.Store.Application;

public static class StoreAutoMapperProfile
{
    /// 
    /// 创建商城领域映射关系
    /// 
    /// 
    public static void CreateStoreMap(this DemoStoreApplicationAutoMapperProfile profile)
    {
        profile.CreateMap()
            .Ignore(x=>x.Products)
            .AfterMap((source, target) =>
            {
                target.Products = new List();
                foreach (var productCount in source.Products)
                {
                    target.Products.Add(new ProductCountDto(productCount.ProductId, productCount.Count));
                }
            });
    }
}

在Demo.Store.Application项目中添加总映射关系类DemoStoreApplicationAutoMapperProfile如下:

using AutoMapper;

namespace Demo.Store.Application;

public class DemoStoreApplicationAutoMapperProfile : Profile
{
    public DemoStoreApplicationAutoMapperProfile()
    {
        this.CreateStoreMap();
    }
}

在Demo.Store.Application项目Store文件夹中添加应用服务实现类StoreOrderAppService如下:

using Demo.OrderManager.Orders;
using Demo.OrderManager.Orders.Dto;
using Demo.ProductManager.Products;
using Demo.ProductManager.Products.Dto;
using Demo.Store.Application.Contracts.Store;
using Demo.Store.Application.Contracts.Store.Dto;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;

namespace Demo.Store.Application;

/// 
/// 订单管理应用服务
/// 
public class StoreOrderAppService : ApplicationService, IStoreOrderAppService
{
    private IOrderAppService _orderAppService;
    private IProductAppService _productAppService;

    public StoreOrderAppService(IOrderAppService orderAppService, IProductAppService productAppService)
    {
        _orderAppService = orderAppService;
        _productAppService = productAppService;
    }

    /// 
    /// 创建订单
    /// 
    /// 输入对象
    /// 创建结果
    public async Task<bool> CreateAsync(StoreOrderCreateDto input)
    {
        foreach (var item in input.Products)
        {
            //判断是否为有效的产品ID
            _productAppService.GetAsync(item.ProductId);
        }

        var order = ObjectMapper.Map(input);
        var ret = await _orderAppService.CreateAsync(order);
        return ret != null;
    }
}

添加完成后启动商城服务、产品管理服务和订单管理三个服务。访问http://localhost:6002/swagger/index.html并测试/api/app/store-order(POST)接口,可创建订单表示服务编写成功。

以相同方式完成该服务其他接口编写。

以相同方式编写后台管理服务用于对外提供对用户角色的管理接口。