Appearance
微服务
AsDI的微服务针对服务本身,当一个微服务被创建时,它首先定义的是此微服务的服务接口。然后将此微服务的接口,发布到本地的NuGet仓库。其它微服务如果需要调用此微服务,只需要引入该微服务的接口即可。
微服务原理
微服务的本质是跨服务的调用。调用过程如下:
- A微服务的API被调用
- A微服务的API调用B微服务的服务接口
- B微服务的接口上带有 [RemoteService] 标识,A微服务自动生成代理类代理当前服务接口
- A微服务的自动代理类将服务的类型、方法、参数等信息进行打包并序列化
- A微服务的自动代理类请求B微服务的服务通道(默认是 /_channel)
- B微服务的服务通道对请求的信息进行反序列化,并实际调用服务实现
- B微服务的服务通道对服务实现返回的信息(包括异常)进行序列化,返回A微服务
- A微服务的自动代理类反序列化返回信息,并将结果返回API
- 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基本示例
项目结构如下图所示

包含两个微服务MasterData和OrderSystem。
MasterData微服务中:
- AsDI.MicroService.MasterData.Api 主数据服务的Web API接口。依赖于AsDI.MicroService.MasterData.IServices和AsDI.MicroService.MasterData.Services
- AsDI.MicroService.MasterData.IServices 主数据服务定义的服务接口。
- AsDI.MicroService.MasterData.Services 主数据服务实现。 依赖于AsDI.MicroService.MasterData.IServices
OrderSystem微服务中:
- AsDI.MicroService.OrderSystem.Api 主数据服务的Web API接口。依赖于AsDI.MicroService.OrderSystem.IServices和AsDI.MicroService.OrderSystem.Services
- AsDI.MicroService.OrderSystem.IServices 主数据服务定义的服务接口。
- 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);
}
}提示
- 在RequestBody中,专门有RequestExtData字段,用于传输自定义请求数据
- 在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>(): " 来实例化