.NET Core中为现有REST端点编写gRPC网关及转换REST API至gRPC服务的可行性与实现方法
嘿,我刚好在.NET生态里折腾过类似的gRPC迁移场景,给你梳理下可行的方案和具体步骤,应该能解决你的疑问!
当然可以!其实有几种不同的实现思路:
- 官方的gRPC HTTP API插件虽然处于实验阶段,但实际小范围使用是没问题的,它能直接把gRPC服务暴露成REST风格的端点,省去自己写网关的麻烦;
- 如果担心实验阶段的稳定性,你可以自己搭建一个独立的网关项目,或者用社区成熟的API网关(比如Ocelot)来实现gRPC请求的转发和转换;
- 另外,也可以在现有服务中直接新增gRPC服务契约,同时保留原有的REST接口,实现双模式支持,再逐步切换调用方。
假设你想搭建一个独立的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服务。
如果想直接把现有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




