.NET Microservices — gRPC in .NET Microservices

Protobuf, Streaming, APIM gRPC Passthrough


Introduction

gRPC (Google Remote Procedure Call) is a high-performance, language-agnostic protocol that uses Protocol Buffers (protobuf) for serialization. It's ideal for:

  • Low-latency communication — Faster than REST
  • Streaming — Bidirectional streaming support
  • Cross-platform — Multiple language support
  • Strong typing — Contract-first development

Protobuf Contract

Define Service

syntax = "proto3";

package order;

service OrderService {
  // Unary call
  rpc CreateOrder (CreateOrderRequest) returns (OrderResponse);
  
  // Server streaming
  rpc GetOrders (GetOrdersRequest) returns (stream Order);
  
  // Client streaming
  rpc ProcessBatch (stream OrderItem) returns (BatchResponse);
  
  // Bidirectional streaming
  rpc StreamOrderUpdates (stream OrderUpdateRequest) returns (stream OrderUpdate);
}

message CreateOrderRequest {
  string customer_id = 1;
  repeated OrderItem items = 2;
  string shipping_address = 3;
}

message OrderItem {
  string product_id = 1;
  int32 quantity = 2;
  double unit_price = 3;
}

message OrderResponse {
  string order_id = 1;
  string status = 2;
  double total = 3;
  google.protobuf.Timestamp created_at = 4;
}

Server Implementation

Create gRPC Service

public class OrderGrpcService : OrderService.OrderServiceBase
{
    private readonly IOrderRepository _repository;
    private readonly ILogger<OrderGrpcService> _logger;
    
    public OrderGrpcService(
        IOrderRepository repository,
        ILogger<OrderGrpcService> logger)
    {
        _repository = repository;
        _logger = logger;
    }
    
    public override async Task<OrderResponse> CreateOrder(
        CreateOrderRequest request,
        ServerCallContext context)
    {
        _logger.LogInformation("Creating order for customer {CustomerId}", 
            request.CustomerId);
        
        var order = new Order
        {
            CustomerId = request.CustomerId,
            ShippingAddress = request.ShippingAddress,
            Items = request.Items.Select(i => new OrderItem
            {
                ProductId = i.ProductId,
                Quantity = i.Quantity,
                UnitPrice = i.UnitPrice
            }).ToList(),
            Status = OrderStatus.Pending,
            CreatedAt = DateTime.UtcNow
        };
        
        await _repository.CreateAsync(order);
        
        return new OrderResponse
        {
            OrderId = order.Id.ToString(),
            Status = order.Status.ToString(),
            Total = order.TotalAmount,
            CreatedAt = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(order.CreatedAt)
        };
    }
    
    public override async Task GetOrders(
        GetOrdersRequest request,
        IServerStreamWriter<Order> responseStream,
        ServerCallContext context)
    {
        var orders = await _repository.GetByCustomerAsync(request.CustomerId);
        
        foreach (var order in orders)
        {
            if (context.CancellationToken.IsCancellationRequested)
                break;
            
            await responseStream.WriteAsync(MapToProto(order));
        }
    }
}

Configure in Program.cs

builder.Services.AddGrpc(options =>
{
    options.MaxReceiveMessageSize = 16 * 1024 * 1024; // 16MB
    options.MaxSendMessageSize = 16 * 1024 * 1024;
    options.Interceptors.Add<LoggingInterceptor>();
});

app.UseEndpoints(endpoints =>
{
    endpoints.MapGrpcService<OrderGrpcService>();
});

Client Implementation

Create gRPC Client

public class OrderGrpcClient
{
    private readonly OrderService.OrderServiceClient _client;
    
    public OrderGrpcClient(GrpcChannel channel)
    {
        _client = new OrderService.OrderServiceClient(channel);
    }
    
    public async Task<OrderResponse> CreateOrderAsync(CreateOrderRequest request)
    {
        return await _client.CreateOrderAsync(request);
    }
    
    public async IAsyncEnumerable<Order> GetOrdersStreamAsync(string customerId)
    {
        var call = _client.GetOrders(new GetOrdersRequest 
        { 
            CustomerId = customerId 
        });
        
        await foreach (var order in call.ResponseStream.ReadAllAsync())
        {
            yield return order;
        }
    }
}

APIM gRPC Passthrough

Configure APIM Policy

<backend>
    <set-backend-service base-url="grpc://order-service:5001" />
    
    <!-- gRPC to HTTP/2 backend -->
    <round-robin>
        <address>https://grpc-primary.example.com</address>
        <address>https://grpc-secondary.example.com</address>
    </round-robin>
</backend>

Best Practices

PracticeDescription
Use streamingFor bulk operations
Set message limitsPrevent large payloads
Add interceptorsLogging, auth, metrics
Use channelsReuse connections

Azure Integration Hub - Advanced Level