Skip to content

微服务

AsDI的微服务针对服务本身,当一个微服务被创建时,它首先定义的是此微服务的服务接口。然后将此微服务的接口,发布到本地的NuGet仓库。其它微服务如果需要调用此微服务,只需要引入该微服务的接口即可。

微服务原理

微服务的本质是跨服务的调用。调用过程如下:

  1. A微服务的API被调用
  2. A微服务的API调用B微服务的服务接口
  3. B微服务的接口上带有 [RemoteService] 标识,A微服务自动生成代理类代理当前服务接口
  4. A微服务的自动代理类将服务的类型、方法、参数等信息进行打包并序列化
  5. A微服务的自动代理类请求B微服务的服务通道(默认是 /_channel)
  6. B微服务的服务通道对请求的信息进行反序列化,并实际调用服务实现
  7. B微服务的服务通道对服务实现返回的信息(包括异常)进行序列化,返回A微服务
  8. A微服务的自动代理类反序列化返回信息,并将结果返回API
  9. A微服务的API将数据返回调用端

引入

使用AsDI的微服务,需要引用AsDI.Core.MicroService,可以通过NuGet进行安装:

sh
dotnet add package AsDI.Core --version 2.0.0
dotnet add package AsDI.Core.Web --version 2.0.0
dotnet add package AsDI.Core.MicroService --version 2.0.0

基本示例

项目结构如下图所示

项目结构

包含两个微服务MasterDataOrderSystem
MasterData微服务中:

  1. AsDI.MicroService.MasterData.Api 主数据服务的Web API接口。依赖于AsDI.MicroService.MasterData.IServices和AsDI.MicroService.MasterData.Services
  2. AsDI.MicroService.MasterData.IServices 主数据服务定义的服务接口。
  3. AsDI.MicroService.MasterData.Services 主数据服务实现。 依赖于AsDI.MicroService.MasterData.IServices

OrderSystem微服务中:

  1. AsDI.MicroService.OrderSystem.Api 主数据服务的Web API接口。依赖于AsDI.MicroService.OrderSystem.IServices和AsDI.MicroService.OrderSystem.Services
  2. AsDI.MicroService.OrderSystem.IServices 主数据服务定义的服务接口。
  3. AsDI.MicroService.OrderSystem.Services 主数据服务实现。依赖于AsDI.MicroService.OrderSystem.IServices和AsDI.MicroService.MasterData.IServices

AsDI.MicroService.MasterData.Api

只需要在Main函数中开启AsDI即可

C#
var app = builder.AsBuild();

AsDI.MicroService.MasterData.IServices

定义一个IUserService服务接口:

C#
namespace AsDI.MicroService.MasterData.IServices
{
    [RemoteService("MasterDataService")]
    public interface IUserService
    {
        /// <summary>
        /// 根据用户Id获取用户信息
        /// </summary>
        /// <param name="id">用户Id</param>
        /// <returns>用户信息</returns>
        UserDto FindByUserId(int id);
    }
}

注意

接口上的 [RemoteService] 标注是必须的,代表当前接口允许微服务间调用。其参数为服务名称。如果使用注册中心,此服务名称必须是注册的服务的名称

AsDI.MicroService.MasterData.Services

实现接口IUserService

C#
namespace AsDI.MicroService.MasterData.Services
{
    [Service]
    public class UserService : IUserService
    {
        public UserDto FindByUserId(int id)
        {
            return new UserDto()
            {
                Id = id,
                Name = "Jack",
                Email = "Jack@xxxx.com"
            };
        }
    }
}

AsDI.MicroService.OrderSystem.Api

需要在Main函数中开启AsDI

C#
var app = builder.AsBuild();

同时,定义了一个Controller用于读取数据

C#
namespace AsDI.MicroService.OrderSystem.Api.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class OrderController : ControllerBase
    {
        private readonly IOrderService orderService;
        public OrderController(IOrderService orderService)
        {
            this.orderService = orderService;
        }

        [HttpGet]
        public OrderDto Get(int id)
        {
            return orderService.FindOrderById(id);
        }
    }
}

AsDI.MicroService.OrderSystem.IServices

定义一个IUserService服务接口:

C#
namespace AsDI.MicroService.OrderSystem.IServices
{
    public interface IOrderService
    {
        OrderDto FindOrderById(int id);

    }
}

由于当前是调用方,其接口可以不必加 [RemoteService] 标注

AsDI.MicroService.OrderSystem.Services

实现接口IUserService

C#

namespace AsDI.MicroService.OrderSystem.Services
{
    [Service]
    public class OrderService : IOrderService
    {
        [AutoAssemble]
        private IUserService userService;

        public OrderDto FindOrderById(int id)
        {
            var orderDto = new OrderDto()
            {
                Id = id,
                Name = "订单1",
                Description = "这是一个模拟的订单",
                Number = 10,
                Price = 10,
                PriceTotal = 100,
                UserId = 1
            };

            var user = userService.FindByUserId(orderDto.UserId);

            orderDto.UserName = user.Name;

            return orderDto;

        }
    }
}

在订单系统中,调用了IUserService,但不必引用IUserService的实现,它会自动跨服务调用相应实现。

配置

AsDI.MicroService.OrderSystem.Api 中,由于需要调用远程服务,所以需要配置远程服务的地址,在appsettings.json中配置如下:

json
{
  "AsDI": {
    "MicroService": {
      "Address": [
        {
          "ServiceName": "MasterDataService",
          "Addresses": [ "http://localhost:5237/" ]
        }
      ]
    }
  }
}

此时就可以远程由OrderSystem服务调用到MasterData服务了。调用OrderController的API接口,可得以下结果,

json
{
  "id": 1,
  "name": "订单1",
  "description": "这是一个模拟的订单",
  "price": 10,
  "number": 10,
  "priceTotal": 100,
  "userId": 1,
  "userName": "Jack"
}

引入注册中心

有的时候我们的服务地址会需要更换,也有的时候由于异常情况,会导致被调用的服务不可用。此时我们需要注册中心,并且从注册中心获取远程服务的地址,既可以实现一个服务的动态扩展,也可以实现微服务在任意服务器上发布。 下面以Nacos为例。

自定义地址翻译器

只需要自定义一个地址翻译器即可,如下:

C#
namespace AsDI.Core.Nacos.MicroService
{
    [Service(2)]
    public class NacosServiceAddressTranslate : IServiceAddressTranslate
    {
        private readonly INacosNamingService _svc;

        public NacosServiceAddressTranslate(INacosNamingService svc)
        {
            _svc = svc;
        }

        public string Translate(string serviceName)
        {
            // 通过分组与服务名获取
            var instance = _svc.SelectOneHealthyInstance(serviceName);
            var result = instance.Result;
            if (result == null)
            {
                throw new Exception($"No available {serviceName} services");
            }
            var host = $"{result.Ip}:{result.Port}";
            var baseUrl = result.Metadata.TryGetValue("secure", out _)
                ? $"https://{host}"
                : $"http://{host}";
            if (string.IsNullOrWhiteSpace(baseUrl))
            {
                return "empty";
            }
            else
            {
                return baseUrl;
            }
        }
    }
}

注意

[Service]的版本需要高于默认翻译器的版本

配置注册中心

配置注册中心请参考注册中心文档即可

请求和响应拦截

有时候需要在请求和响应时,对信息进行处理。此时,可以自定义请求拦截器和响应拦截器。

请求拦截器(在调用方执行),需要继承IRequestInterceptor,例如:

C#
[Service]
public class RequestInterceptor : IRequestInterceptor
{
    /// <summary>
    /// 在请求之前执行
    /// </summary>
    /// <param name="request">请求内容</param>
    public void BeforeRequest(RequestBody request)
    {
        request.RequestExtData = "abc";
    }
    /// <summary>
    /// 在请求结束后执行
    /// </summary>
    /// <param name="request">请求内容</param>
    /// <param name="response">响应内容</param>
    public void AfterRequest(RequestBody request, ResponseResult response)
    {
        Console.WriteLine(response.ResponseExtData);
    }

}

提示

  1. 在RequestBody中,专门有RequestExtData字段,用于传输自定义请求数据
  2. 在ResponseResult中,专门有ResponseExtData字段,用于传输自定义响应数据 以上字段类型都是字符串,如果需要传输数据对象,自行序列化和反序列化即可

响应拦截器(在被调用方执行),需要继承IResponseInterceptor,例如:

C#
[Service]
public class ResponseInterceptor : IResponseInterceptor
{
    /// <summary>
    /// 收到请求后执行
    /// </summary>
    /// <param name="request">请求内容</param>
    public void RequestReceive(RequestBody request)
    {
        Console.WriteLine(request.RequestExtData);
    }
    /// <summary>
    /// 返回响应内容前执行
    /// </summary>
    /// <param name="request">请求内容</param>
    /// <param name="response">响应内容</param>
    public void BeforeResponse(RequestBody request, ResponseResult response)
    {
        response.ResponseExtData = "123";
    }
}

拦截器的执行顺序是:

BeforeRequest -> RequestReceive -> BeforeResponse -> AfterRequest

自定义传输通道

默认的传输方式是Rest API的方式,如果微服务间的调用方式,想换成其它方式,可以自定义传输通道。

自定义IServiceChannel

首先需要自定义IServiceChannel接口的实例

C#
public class ServiceChannel : IServiceChannel
{
   public ResponseResult? Invoke(string serviceName, RequestBody? request)
   {
       //书写自己的数据传输方式
       //1、自定义根据 serviceName 查询远程地址的方法
       //2、将request的内容,按自己的方式传输
   }
}

自定义自己的接收方式

不同的协议类型的数据接收方式不一样,需要自己定义,假设有以下方式:

C#
[Include]
public class TcpReceiver
{
   [AutoAssemble]
   private static IServiceInvoker invoker;

   public byte[] OnDataReceive(byte[] data)
   {
       RequestBody requestBody = ToRequestBody(data);
       var result = invoker.Invoke(requestBody);
       byte[] rtn = ResponseToByte(result);
       return rtn;
   }
}

注意

自定义的接收方式中, IServiceInvoker用于调用接口的实现, 如果需要用到IServiceInvoker需要通过static注入,除非你的TcpReceiver是通过AsDI来实例化的。也可以直接在需要IServiceInvoker的地方,使用语句 "var invoker=AsDIContext.Get<IServiceInvoker>(): " 来实例化

沪ICP备2025119739号