grpc-services

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

gRPC Services in .NET

.NET 中的 gRPC 服务

Proto File

Proto 文件

protobuf
syntax = "proto3";
option csharp_namespace = "MyApp.Grpc";
package catalog;

service CatalogService {
  rpc GetProduct (GetProductRequest) returns (ProductResponse);
  rpc ListProducts (ListProductsRequest) returns (ListProductsResponse);
  rpc CreateProduct (CreateProductRequest) returns (ProductResponse);
  rpc StreamProducts (StreamRequest) returns (stream ProductResponse);
}

message GetProductRequest { int32 id = 1; }
message ListProductsRequest {
  int32 page = 1;
  int32 page_size = 2;
  string search = 3;
}
message ListProductsResponse {
  repeated ProductResponse products = 1;
  int32 total_count = 2;
}
message CreateProductRequest {
  string name = 1;
  string description = 2;
  double price = 3;
}
message ProductResponse {
  int32 id = 1;
  string name = 2;
  string description = 3;
  double price = 4;
}
message StreamRequest { string filter = 1; }
protobuf
syntax = "proto3";
option csharp_namespace = "MyApp.Grpc";
package catalog;

service CatalogService {
  rpc GetProduct (GetProductRequest) returns (ProductResponse);
  rpc ListProducts (ListProductsRequest) returns (ListProductsResponse);
  rpc CreateProduct (CreateProductRequest) returns (ProductResponse);
  rpc StreamProducts (StreamRequest) returns (stream ProductResponse);
}

message GetProductRequest { int32 id = 1; }
message ListProductsRequest {
  int32 page = 1;
  int32 page_size = 2;
  string search = 3;
}
message ListProductsResponse {
  repeated ProductResponse products = 1;
  int32 total_count = 2;
}
message CreateProductRequest {
  string name = 1;
  string description = 2;
  double price = 3;
}
message ProductResponse {
  int32 id = 1;
  string name = 2;
  string description = 3;
  double price = 4;
}
message StreamRequest { string filter = 1; }

Service Implementation

服务实现

csharp
public sealed class CatalogGrpcService(ICatalogService catalog, ILogger<CatalogGrpcService> logger)
    : CatalogService.CatalogServiceBase
{
    public override async Task<ProductResponse> GetProduct(
        GetProductRequest request, ServerCallContext context)
    {
        var product = await catalog.GetByIdAsync(request.Id, context.CancellationToken)
            ?? throw new RpcException(new Status(StatusCode.NotFound, $"Product {request.Id} not found"));

        return MapToResponse(product);
    }

    public override async Task StreamProducts(
        StreamRequest request, IServerStreamWriter<ProductResponse> responseStream, ServerCallContext context)
    {
        await foreach (var product in catalog.StreamAsync(request.Filter, context.CancellationToken))
        {
            await responseStream.WriteAsync(MapToResponse(product));
        }
    }
}
csharp
public sealed class CatalogGrpcService(ICatalogService catalog, ILogger<CatalogGrpcService> logger)
    : CatalogService.CatalogServiceBase
{
    public override async Task<ProductResponse> GetProduct(
        GetProductRequest request, ServerCallContext context)
    {
        var product = await catalog.GetByIdAsync(request.Id, context.CancellationToken)
            ?? throw new RpcException(new Status(StatusCode.NotFound, $"Product {request.Id} not found"));

        return MapToResponse(product);
    }

    public override async Task StreamProducts(
        StreamRequest request, IServerStreamWriter<ProductResponse> responseStream, ServerCallContext context)
    {
        await foreach (var product in catalog.StreamAsync(request.Filter, context.CancellationToken))
        {
            await responseStream.WriteAsync(MapToResponse(product));
        }
    }
}

Server Setup

服务器配置

csharp
builder.Services.AddGrpc();
app.MapGrpcService<CatalogGrpcService>();
csharp
builder.Services.AddGrpc();
app.MapGrpcService<CatalogGrpcService>();

Client (with Aspire)

客户端(搭配Aspire)

csharp
builder.Services.AddGrpcClient<CatalogService.CatalogServiceClient>(o =>
{
    o.Address = new("https+http://catalog-api");
})
.AddStandardResilienceHandler();
csharp
builder.Services.AddGrpcClient<CatalogService.CatalogServiceClient>(o =>
{
    o.Address = new("https+http://catalog-api");
})
.AddStandardResilienceHandler();

gRPC vs REST Decision Matrix (from official docs)

gRPC vs REST 决策矩阵(来自官方文档)

AspectgRPCREST/HTTP
SerializationProtocol Buffers (binary)JSON (text)
ProtocolHTTP/2HTTP/1.1 or HTTP/2
Payload sizeSmall, compressedLarger, human-readable
LatencyLow (~10x faster)Higher
Browser supportLimited (gRPC-Web)Full
Code generationAutomatic (contract-first)Manual
StreamingNative, bidirectionalLimited
DebuggingBinary (harder)Easy (HTTP tools)
Use caseMicroservices, real-timePublic APIs, browsers
维度gRPCREST/HTTP
序列化方式Protocol Buffers(二进制)JSON(文本)
协议HTTP/2HTTP/1.1 或 HTTP/2
负载大小小体积、已压缩体积较大、人类可读
延迟低(约快10倍)较高
浏览器支持有限(需使用gRPC-Web)完全支持
代码生成自动生成(契约优先)手动编写
流处理原生支持、双向有限支持
调试难度二进制格式(较难)简单(可使用HTTP工具)
适用场景微服务、实时通信公开API、浏览器端

All 4 RPC Patterns (from official docs)

四种RPC模式(来自官方文档)

Client Streaming

客户端流模式

csharp
// Stream of requests → single response
public override async Task<HelloReply> LotsOfGreetings(
    IAsyncStreamReader<HelloRequest> requestStream, ServerCallContext context)
{
    var count = 0;
    await foreach (var request in requestStream.ReadAllAsync())
    {
        count++;
    }
    return new HelloReply { Message = $"Processed {count} greetings" };
}
csharp
// 请求流 → 单个响应
public override async Task<HelloReply> LotsOfGreetings(
    IAsyncStreamReader<HelloRequest> requestStream, ServerCallContext context)
{
    var count = 0;
    await foreach (var request in requestStream.ReadAllAsync())
    {
        count++;
    }
    return new HelloReply { Message = $"Processed {count} greetings" };
}

Bidirectional Streaming

双向流模式

csharp
// Stream requests ↔ stream responses simultaneously
public override async Task BidiHello(
    IAsyncStreamReader<HelloRequest> requestStream,
    IServerStreamWriter<HelloReply> responseStream,
    ServerCallContext context)
{
    await foreach (var request in requestStream.ReadAllAsync())
    {
        await responseStream.WriteAsync(new HelloReply
        {
            Message = $"Echo: {request.Name}"
        });
    }
}
csharp
// 请求流 ↔ 响应流同时进行
public override async Task BidiHello(
    IAsyncStreamReader<HelloRequest> requestStream,
    IServerStreamWriter<HelloReply> responseStream,
    ServerCallContext context)
{
    await foreach (var request in requestStream.ReadAllAsync())
    {
        await responseStream.WriteAsync(new HelloReply
        {
            Message = $"Echo: {request.Name}"
        });
    }
}

Project Setup (from official docs)

项目配置(来自官方文档)

xml
<!-- .csproj -->
<ItemGroup>
  <Protobuf Include="Protos\greet.proto" />
</ItemGroup>
bash
dotnet add package Grpc.AspNetCore
dotnet add package Grpc.Tools
xml
<!-- .csproj -->
<ItemGroup>
  <Protobuf Include="Protos\greet.proto" />
</ItemGroup>
bash
dotnet add package Grpc.AspNetCore
dotnet add package Grpc.Tools

Best Practices

最佳实践

  • Use gRPC for internal service-to-service communication
  • Use REST/HTTP for external APIs (browser compatibility)
  • Use server streaming for large result sets
  • Use
    Deadline
    for request timeouts
  • Use interceptors for cross-cutting concerns (logging, auth)
  • Add
    .proto
    files to
    <Protobuf>
    item group in .csproj
  • Binary format = harder debugging; use gRPC reflection for tooling
  • 内部服务间通信使用gRPC
  • 外部API使用REST/HTTP(考虑浏览器兼容性)
  • 处理大型结果集时使用服务器流模式
  • 为请求设置
    Deadline
    超时时间
  • 使用拦截器处理横切关注点(日志、认证)
  • .proto
    文件添加到.csproj的
    <Protobuf>
    项组中
  • 二进制格式调试难度较高;使用gRPC反射工具辅助调试