You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

.NET Core中为现有REST端点编写gRPC网关及转换REST API至gRPC服务的可行性与实现方法

嘿,我刚好在.NET生态里折腾过类似的gRPC迁移场景,给你梳理下可行的方案和具体步骤,应该能解决你的疑问!

1. .NET Core中能不能编写gRPC网关?

当然可以!其实有几种不同的实现思路:

  • 官方的gRPC HTTP API插件虽然处于实验阶段,但实际小范围使用是没问题的,它能直接把gRPC服务暴露成REST风格的端点,省去自己写网关的麻烦;
  • 如果担心实验阶段的稳定性,你可以自己搭建一个独立的网关项目,或者用社区成熟的API网关(比如Ocelot)来实现gRPC请求的转发和转换;
  • 另外,也可以在现有服务中直接新增gRPC服务契约,同时保留原有的REST接口,实现双模式支持,再逐步切换调用方。
2. 如何为现有HTTP REST API编写gRPC网关?

假设你想搭建一个独立的gRPC网关,把外部的gRPC请求转换成内部的REST调用,可以按以下步骤来:

  • 第一步:创建网关项目并引入依赖
    创建一个.NET Core Web API项目,安装必要的NuGet包:Grpc.AspNetCore(用于提供gRPC服务)、System.Net.Http.Json(用于调用REST接口)。

  • 第二步:定义gRPC服务契约
    编写.proto文件,对应现有REST接口的功能。比如针对BillingService的GET v1/billing/{id},可以定义:

    syntax = "proto3";
    
    option csharp_namespace = "BillingGateway.Grpc";
    
    service BillingService {
      rpc GetBillingDetail (GetBillingRequest) returns (BillingResponse);
    }
    
    message GetBillingRequest {
      string billing_id = 1;
    }
    
    message BillingResponse {
      string billing_id = 1;
      double price = 2;
      string status = 3;
    }
    

    然后在项目文件中配置生成gRPC代码:

    <ItemGroup>
      <Protobuf Include="Protos\billing.proto" GrpcServices="Server" />
    </ItemGroup>
    
  • 第三步:实现gRPC网关服务
    创建一个gRPC服务类,继承自动生成的BillingService.BillingServiceBase,在方法内部调用现有的REST接口,把REST响应转换成gRPC消息返回:

    public class BillingGatewayService : BillingService.BillingServiceBase
    {
        private readonly HttpClient _httpClient;
    
        public BillingGatewayService(HttpClient httpClient)
        {
            _httpClient = httpClient;
        }
    
        public override async Task<BillingResponse> GetBillingDetail(GetBillingRequest request, ServerCallContext context)
        {
            // 调用原REST接口
            var restResponse = await _httpClient.GetFromJsonAsync<BillingRestResponse>($"v1/billing/{request.BillingId}");
            
            // 转换为gRPC响应
            return new BillingResponse
            {
                BillingId = restResponse.BillingId,
                Price = restResponse.Price,
                Status = restResponse.Status
            };
        }
    
        // 对应REST响应的DTO
        private class BillingRestResponse
        {
            public string BillingId { get; set; }
            public double Price { get; set; }
            public string Status { get; set; }
        }
    }
    
  • 第四步:配置网关
    Program.cs中注册gRPC服务和HttpClient:

    var builder = WebApplication.CreateBuilder(args);
    
    // 添加gRPC服务
    builder.Services.AddGrpc();
    // 注册HttpClient用于调用原REST服务
    builder.Services.AddHttpClient("BillingRestService", client =>
    {
        client.BaseAddress = new Uri("http://your-billing-service-url/");
    });
    
    var app = builder.Build();
    
    // 映射gRPC服务
    app.MapGrpcService<BillingGatewayService>();
    
    app.Run();
    

    这样,外部的gRPC客户端(比如ProductService)就可以调用网关的gRPC接口,网关内部转发到原REST服务。

3. 如何将现有REST API转换为gRPC服务?

如果想直接把现有REST服务改成gRPC服务(推荐长期方案),步骤如下:

  • 第一步:定义gRPC契约
    和上面的网关类似,先根据现有REST接口的输入输出编写.proto文件。比如ProductService调用的GET v1/billing/{id},对应的gRPC契约就是上面那个.proto

  • 第二步:在现有服务中添加gRPC实现
    在BillingService项目中引入Grpc.AspNetCore包,添加.proto文件并配置生成代码。然后创建gRPC服务类,复用原REST接口的业务逻辑:

    public class BillingGrpcService : BillingService.BillingServiceBase
    {
        // 假设原REST接口的业务逻辑在这个服务类里
        private readonly IBillingBusinessService _billingService;
    
        public BillingGrpcService(IBillingBusinessService billingService)
        {
            _billingService = billingService;
        }
    
        public override async Task<BillingResponse> GetBillingDetail(GetBillingRequest request, ServerCallContext context)
        {
            // 复用原业务逻辑
            var billingDetail = await _billingService.GetBillingByIdAsync(request.BillingId);
            
            return new BillingResponse
            {
                BillingId = billingDetail.Id,
                Price = billingDetail.Price,
                Status = billingDetail.Status.ToString()
            };
        }
    }
    
  • 第三步:配置gRPC服务
    在BillingService的Program.cs中添加gRPC支持:

    var builder = WebApplication.CreateBuilder(args);
    
    // 添加gRPC服务
    builder.Services.AddGrpc();
    // 注册原有业务服务
    builder.Services.AddScoped<IBillingBusinessService, BillingBusinessService>();
    
    var app = builder.Build();
    
    // 映射gRPC服务
    app.MapGrpcService<BillingGrpcService>();
    // 保留原REST接口(可选,逐步迁移)
    app.MapGet("v1/billing/{id}", async (string id, IBillingBusinessService service) =>
    {
        var detail = await service.GetBillingByIdAsync(id);
        return Results.Ok(detail);
    });
    
    app.Run();
    
  • 第四步:修改调用方(ProductService)
    在ProductService中引入Grpc.Net.Client包,替换原来的HttpClient调用,改用gRPC客户端:

    public class ProductService
    {
        private readonly BillingService.BillingServiceClient _billingClient;
    
        public ProductService(GrpcChannel channel)
        {
            _billingClient = new BillingService.BillingServiceClient(channel);
        }
    
        public async Task<ProductDetail> GetProductBedDetail(string productId)
        {
            // 调用gRPC接口
            var billingResponse = await _billingClient.GetBillingDetailAsync(new GetBillingRequest { BillingId = "b1" });
            
            return new ProductDetail
            {
                ProductId = productId,
                Price = billingResponse.Price,
                // 其他字段...
            };
        }
    }
    

    然后在Program.cs中注册gRPC通道:

    builder.Services.AddSingleton(services =>
    {
        var channel = GrpcChannel.ForAddress("http://your-billing-grpc-service-url/");
        return channel;
    });
    builder.Services.AddScoped<ProductService>();
    
一些额外建议
  • 如果不想一次性全量迁移,可以先让BillingService同时支持REST和gRPC,然后逐步把ProductService的调用从REST切换到gRPC;
  • 关于gRPC网关,如果觉得自己写麻烦,可以试试Ocelot网关,它支持配置gRPC路由,直接把gRPC请求转发到对应的gRPC服务;
  • 官方的gRPC HTTP API插件虽然是实验阶段,但功能已经比较完善,如果你需要让gRPC服务同时对外暴露REST接口,可以试试这个,只需要在Program.cs中添加builder.Services.AddGrpcHttpApi();,然后映射的时候加上app.MapGrpcService<BillingGrpcService>().EnableGrpcHttpApi();,就能自动生成REST端点;
  • 编写.proto文件时,尽量遵循gRPC的命名规范,比如字段名用蛇形(billing_id),对应.NET的驼峰属性(BillingId),工具会自动映射。

内容的提问来源于stack exchange,提问作者Sannn

火山引擎 最新活动