.NET gRPC Microsserviços C# Backend

gRPC no .NET: comunicação eficiente entre microsserviços com Protobuf e streaming

Como implementar gRPC em .NET: definição de contratos com Protobuf, serviços unários e streaming, autenticação.

N
Neryx Digital Architects
18 de novembro de 2025
12 min de leitura
190 profissionais leram
Categoria: Arquitetura Público: Times de engenharia e produto Etapa: Aprendizado

gRPC é o protocolo de comunicação inter-serviço mais eficiente para microsserviços .NET. Enquanto REST usa JSON sobre HTTP/1.1, gRPC usa Protobuf binário sobre HTTP/2 — gerando payloads até 10× menores e latência mais baixa. O contrato é definido em .proto, e o código C# é gerado automaticamente: zero chance de inconsistência entre cliente e servidor.

Setup: servidor gRPC em ASP.NET Core

dotnet add package Grpc.AspNetCore

// Program.cs: builder.Services.AddGrpc(options => { options.MaxReceiveMessageSize = 4 * 1024 * 1024; // 4MB options.MaxSendMessageSize = 4 * 1024 * 1024; options.EnableDetailedErrors = builder.Environment.IsDevelopment(); });

builder.Services.AddGrpcReflection(); // permite inspeção via Postman/grpcurl em dev

app.MapGrpcService<ProdutosGrpcService>(); app.MapGrpcService<PedidosGrpcService>();

if (app.Environment.IsDevelopment()) app.MapGrpcReflectionService(); // /grpc.reflection.v1alpha.ServerReflection

Definindo o contrato com Protobuf

// Protos/produtos.proto:
syntax = "proto3";
option csharp_namespace = "NeryxApp.Grpc";
package produtos;

service ProdutosService {
    // Unário: request → response (como REST)
    rpc GetProduto (GetProdutoRequest) returns (ProdutoResponse);
    rpc CriarProduto (CriarProdutoRequest) returns (ProdutoResponse);

    // Server streaming: request → múltiplos responses
    rpc ListarProdutos (ListarProdutosRequest) returns (stream ProdutoResponse);

    // Client streaming: múltiplos requests → response
    rpc ImportarProdutos (stream CriarProdutoRequest) returns (ImportacaoResponse);

    // Bidirecional: múltiplos requests ↔ múltiplos responses
    rpc SincronizarEstoque (stream EstoqueUpdate) returns (stream EstoqueConfirmacao);
}

message GetProdutoRequest {
    string id = 1; // campo 1: id (number = tag, não o valor)
}

message CriarProdutoRequest {
    string nome = 1;
    double preco = 2;
    string categoria = 3;
    int32 estoque = 4;
}

message ProdutoResponse {
    string id = 1;
    string nome = 2;
    double preco = 3;
    string categoria = 4;
    int32 estoque = 5;
    string criado_em = 6;
}

message ListarProdutosRequest {
    string categoria = 1;    // filtro opcional
    int32 page_size = 2;     // default 0 = sem limite
}

message ImportacaoResponse {
    int32 total_importados = 1;
    int32 total_erros = 2;
    repeated string erros = 3;
}

message EstoqueUpdate {
    string produto_id = 1;
    int32 quantidade = 2;
    string operacao = 3; // "incrementar" | "decrementar" | "definir"
}

message EstoqueConfirmacao {
    string produto_id = 1;
    int32 estoque_atual = 2;
    bool sucesso = 3;
    string mensagem = 4;
}
// .csproj — configurar geração automática de código C#:
<ItemGroup>
    <Protobuf Include="Protos/produtos.proto" GrpcServices="Server" />
    <!-- GrpcServices="Client" para gerar apenas cliente -->
    <!-- GrpcServices="Both" para gerar ambos -->
</ItemGroup>

Implementando o serviço (servidor)

// A classe base ProdutosServiceBase é gerada automaticamente pelo Protobuf:
public class ProdutosGrpcService : ProdutosService.ProdutosServiceBase
{
    private readonly IProdutoRepository _repo;
public ProdutosGrpcService(IProdutoRepository repo) => _repo = repo;

// Unário:
public override async Task&lt;ProdutoResponse&gt; GetProduto(
    GetProdutoRequest request, ServerCallContext context)
{
    var produto = await _repo.GetByIdAsync(Guid.Parse(request.Id),
        context.CancellationToken);

    if (produto is null)
        throw new RpcException(new Status(StatusCode.NotFound,
            $"Produto {request.Id} não encontrado"));

    return MapToResponse(produto);
}

// Server streaming — envia produtos um por um:
public override async Task ListarProdutos(
    ListarProdutosRequest request,
    IServerStreamWriter&lt;ProdutoResponse&gt; responseStream,
    ServerCallContext context)
{
    // Busca em lotes para não carregar tudo em memória:
    await foreach (var produto in _repo.StreamByCategoriaAsync(
        request.Categoria, context.CancellationToken))
    {
        if (context.CancellationToken.IsCancellationRequested) break;
        await responseStream.WriteAsync(MapToResponse(produto));
    }
}

// Client streaming — recebe lote de criações:
public override async Task&lt;ImportacaoResponse&gt; ImportarProdutos(
    IAsyncStreamReader&lt;CriarProdutoRequest&gt; requestStream,
    ServerCallContext context)
{
    var importados = 0;
    var erros = new List&lt;string&gt;();

    await foreach (var request in requestStream.ReadAllAsync(context.CancellationToken))
    {
        try
        {
            await _repo.CriarAsync(MapToEntity(request), context.CancellationToken);
            importados++;
        }
        catch (Exception ex)
        {
            erros.Add($"Erro ao importar '{request.Nome}': {ex.Message}");
        }
    }

    return new ImportacaoResponse
    {
        TotalImportados = importados,
        TotalErros = erros.Count,
        Erros = { erros }
    };
}

// Streaming bidirecional — sincronização de estoque:
public override async Task SincronizarEstoque(
    IAsyncStreamReader&lt;EstoqueUpdate&gt; requestStream,
    IServerStreamWriter&lt;EstoqueConfirmacao&gt; responseStream,
    ServerCallContext context)
{
    await foreach (var update in requestStream.ReadAllAsync(context.CancellationToken))
    {
        var estoque = await _repo.AtualizarEstoqueAsync(
            Guid.Parse(update.ProdutoId), update.Quantidade, update.Operacao);

        await responseStream.WriteAsync(new EstoqueConfirmacao
        {
            ProdutoId = update.ProdutoId,
            EstoqueAtual = estoque,
            Sucesso = true
        });
    }
}

private static ProdutoResponse MapToResponse(Produto p) => new()
{
    Id = p.Id.ToString(),
    Nome = p.Nome,
    Preco = (double)p.Preco,
    Categoria = p.Categoria,
    Estoque = p.Estoque,
    CriadoEm = p.CriadoEm.ToString("O")
};

}

Cliente gRPC em outro microsserviço

dotnet add package Grpc.Net.Client
dotnet add package Google.Protobuf
dotnet add package Grpc.Tools

// Registrar cliente com HttpClientFactory (recomendado em produção): builder.Services.AddGrpcClient<ProdutosService.ProdutosServiceClient>(options => { options.Address = new Uri(builder.Configuration[“Services:Produtos:GrpcUrl”]!); // Ex: “https://produtos-service:5001” }) .ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler { PooledConnectionIdleTimeout = Timeout.InfiniteTimeSpan, KeepAlivePingDelay = TimeSpan.FromSeconds(60), KeepAlivePingTimeout = TimeSpan.FromSeconds(30), EnableMultipleHttp2Connections = true // connection pooling HTTP/2 }) .AddCallCredentials((context, metadata) => { // Propaga token JWT do request atual para o microsserviço downstream var httpContext = context.ServiceProvider .GetRequiredService<IHttpContextAccessor>().HttpContext; var token = httpContext?.Request.Headers[“Authorization”].ToString() .Replace(“Bearer ”, ""); if (!string.IsNullOrEmpty(token)) metadata.Add(“Authorization”, $“Bearer {token}”); return Task.CompletedTask; }, GrpcChannelOptions.Empty);

// Uso no serviço: public class PedidoService { private readonly ProdutosService.ProdutosServiceClient _produtosClient;

public PedidoService(ProdutosService.ProdutosServiceClient client)
    => _produtosClient = client;

public async Task&lt;PedidoDto&gt; CriarPedidoAsync(CriarPedidoRequest request, CancellationToken ct)
{
    // Valida produto via gRPC (comunicação interna eficiente)
    var produto = await _produtosClient.GetProdutoAsync(
        new GetProdutoRequest { Id = request.ProdutoId },
        cancellationToken: ct);

    if (produto.Estoque &lt; request.Quantidade)
        throw new InvalidOperationException("Estoque insuficiente");

    // ... cria pedido
}

}

Interceptors: cross-cutting concerns no gRPC

// Equivalente ao middleware ASP.NET Core, mas para gRPC:
public class LoggingInterceptor : Interceptor
{
    private readonly ILogger<LoggingInterceptor> _logger;
public LoggingInterceptor(ILogger&lt;LoggingInterceptor&gt; logger) => _logger = logger;

// Intercepta chamadas unárias:
public override async Task&lt;TResponse&gt; UnaryServerHandler&lt;TRequest, TResponse&gt;(
    TRequest request,
    ServerCallContext context,
    UnaryServerMethod&lt;TRequest, TResponse&gt; continuation)
{
    var sw = Stopwatch.StartNew();
    try
    {
        var response = await continuation(request, context);
        _logger.LogInformation("gRPC {Method} concluído em {Ms}ms",
            context.Method, sw.ElapsedMilliseconds);
        return response;
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "gRPC {Method} falhou após {Ms}ms",
            context.Method, sw.ElapsedMilliseconds);
        throw;
    }
}

}

// Registrar globalmente: builder.Services.AddGrpc(options => options.Interceptors.Add<LoggingInterceptor>());

gRPC vs REST vs Mensageria: quando usar cada um

gRPC é ideal para comunicação síncrona entre microsserviços internos onde performance importa — payloads menores, streaming bidirecional, contrato fortemente tipado. Use quando dois serviços precisam de resposta imediata e rodam na mesma rede privada.

REST/HTTP é a escolha para APIs públicas consumidas por apps web e mobile — amplamente suportado, fácil de debugar com ferramentas padrão, sem dependência de cliente gerado.

Mensageria (RabbitMQ, SQS) é para comunicação assíncrona onde o produtor não precisa esperar o consumidor — processamento em background, eventos de domínio, fanout para múltiplos consumidores.

Conclusão

gRPC no .NET elimina a inconsistência de contratos entre microsserviços — o código C# é gerado a partir do .proto, então quebras de contrato viram erros de compilação antes de chegar em produção. O streaming bidirecional resolve casos como sincronização de dados e dashboards internos de forma nativa. Para comunicação interna entre serviços .NET, gRPC é a escolha mais robusta em 2026.

Se você está desenhando a comunicação entre microsserviços e precisa decidir entre gRPC, REST e mensageria, a Neryx pode ajudar com a análise e implementação. Consultoria inicial gratuita.

Leitura complementar:

Precisa desenhar a próxima fase com menos retrabalho?

Fazemos discovery técnico para mapear riscos, arquitetura-alvo e sequência de execução antes de investir pesado.

Solicitar Discovery

Newsletter

Receba artigos como este no seu e-mail

Conteúdo técnico sobre arquitetura de software, .NET, IA e gestão de produto. Sem spam.