MÓDULO 09 · CONCEITO 03 DE 14

gRPC e Protocol Buffers

.proto · geração de código · 4 modos de streaming · quando gRPC vence REST

Tempo de leitura ~22 min Pré-requisito Conceito 02 — REST em profundidade Próximo 04 · GraphQL

Anatomia do arquivo .proto

O arquivo .proto é o contrato central de um serviço gRPC. Ele define mensagens, campos, tipos e os serviços (conjuntos de RPCs) que o servidor implementa e os clientes chamam. Ao contrário de um contrato OpenAPI que descreve endpoints HTTP, um .proto descreve chamadas de procedimento com semântica forte de tipos.

syntax = "proto3";

package orders.v1;

option go_package = "github.com/empresa/orders/gen/orders/v1;ordersv1";
option csharp_namespace = "Empresa.Orders.V1";

import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";

// Enum de status do pedido
enum OrderStatus {
  ORDER_STATUS_UNSPECIFIED = 0; // sempre zero para proto3
  ORDER_STATUS_PENDING = 1;
  ORDER_STATUS_PROCESSING = 2;
  ORDER_STATUS_SHIPPED = 3;
  ORDER_STATUS_DELIVERED = 4;
  ORDER_STATUS_CANCELLED = 5;
}

// Mensagem de item de pedido
message OrderItem {
  string product_id = 1;
  int32 quantity = 2;
  int64 unit_price_cents = 3; // preço em centavos para evitar float
}

// Mensagem principal de pedido
message Order {
  string id = 1;
  string customer_id = 2;
  repeated OrderItem items = 3; // lista de itens
  OrderStatus status = 4;
  google.protobuf.Timestamp created_at = 5;
  google.protobuf.Timestamp updated_at = 6;
  map<string, string> metadata = 7; // campo map
}

// Request para criação
message CreateOrderRequest {
  string customer_id = 1;
  repeated OrderItem items = 2;
  map<string, string> metadata = 3;
}

// Response de criação
message CreateOrderResponse {
  Order order = 1;
}

// Request para busca por ID
message GetOrderRequest {
  string id = 1;
}

// Request para stream de eventos
message WatchOrderRequest {
  string order_id = 1;
}

// Evento de mudança de status
message OrderEvent {
  string order_id = 1;
  OrderStatus previous_status = 2;
  OrderStatus new_status = 3;
  google.protobuf.Timestamp occurred_at = 4;
}

// Request para busca por cliente (lista)
message ListOrdersByCustomerRequest {
  string customer_id = 1;
  int32 page_size = 2;
  string page_token = 3;
}

// Response paginado
message ListOrdersByCustomerResponse {
  repeated Order orders = 1;
  string next_page_token = 2;
}

// Definição do serviço
service OrderService {
  // Unary RPC — padrão request-response
  rpc CreateOrder(CreateOrderRequest) returns (CreateOrderResponse);
  rpc GetOrder(GetOrderRequest) returns (Order);

  // Server streaming — servidor envia múltiplas mensagens
  rpc WatchOrder(WatchOrderRequest) returns (stream OrderEvent);

  // Server streaming para paginação natural
  rpc ListOrdersByCustomer(ListOrdersByCustomerRequest) returns (stream Order);

  // Client streaming — cliente envia múltiplos itens, servidor responde uma vez
  rpc BulkCreateOrders(stream CreateOrderRequest) returns (CreateOrderResponse);

  // Bidirectional streaming — ambos enviam/recebem livremente
  rpc SyncOrderStatus(stream WatchOrderRequest) returns (stream OrderEvent);
}
nota Numeração de campos: os números (= 1, = 2, etc.) são os identificadores que aparecem no encoding binário — não os valores. Uma vez publicados em produção, jamais podem ser reutilizados com outro tipo, pois clientes antigos interpretariam o campo com semântica errada. Campos removidos devem ser marcados como reserved 3, 5 to 7; e reserved "old_field_name"; para evitar reuso acidental.

Tipos escalares e suas correspondências

Proto3 define tipos escalares com semântica precisa. A tabela abaixo mostra os mais usados e como mapeiam para cada linguagem:

Tipo proto3C#PythonGoUso típico
stringstringstrstringIDs, nomes, texto UTF-8
int32intintint32Contadores, quantidades
int64longintint64Timestamps Unix, preços em centavos
uint32/uint64uint/ulongintuint32/uint64Valores sempre positivos
boolboolboolboolFlags
bytesByteStringbytes[]byteDados binários arbitrários
double/floatdouble/floatfloatfloat64/float32Evitar para valores monetários
atenção Proto3 e valores default: em proto3, todos os campos têm valor zero quando não setados — 0 para numéricos, "" para string, false para bool. Isso significa que você não consegue distinguir "não enviou" de "enviou zero/vazio". Para campos opcionais onde a ausência tem semântica, use optional int32 quantity = 1; (gera wrapper nullable na linguagem) ou use google.protobuf.Int32Value (wrapper type explícito).

Well-known types e Google APIs

O pacote google/protobuf/ fornece tipos reutilizáveis que evitam reinventar estruturas comuns:

Encoding binário do Protocol Buffers

A eficiência do gRPC vem em parte do encoding binário dos Protocol Buffers. Entender como o encoding funciona explica por que gRPC é substancialmente mais compacto que JSON e por que certas decisões de design no schema impactam performance.

Wire types e field tags

Cada campo serializado é precedido por um tag — um varint que codifica o field number e o wire type. Wire types determinam como ler os bytes seguintes:

Wire TypeIDUsado para
Varint0int32, int64, uint32, uint64, sint32, sint64, bool, enum
64-bit1fixed64, sfixed64, double
Length-delimited2string, bytes, embedded messages, packed repeated fields
32-bit5fixed32, sfixed32, float

O tag é calculado como (field_number << 3) | wire_type. Para o campo customer_id = 2 (string → wire type 2), o tag é (2 << 3) | 2 = 18 = 0x12.

Varint encoding

Varints são inteiros de tamanho variável: cada byte usa 7 bits para dados e 1 bit para indicar se há mais bytes. Números pequenos (0–127) ocupam 1 byte; números médios, 2 bytes; grandes, até 10 bytes. Isso é extremamente eficiente quando a maioria dos valores são pequenos — o caso típico em sistemas reais.

Para inteiros negativos, int32 é ineficiente (sempre 10 bytes por usar complemento de dois). Use sint32/sint64 que aplicam ZigZag encoding: n → (n << 1) ^ (n >> 31), mapeando negativos para positivos pequenos.

Comparação de tamanho: JSON vs Protobuf

Considere o objeto Order com id, customer_id, 3 itens, status e timestamps. Em JSON, o payload seria aproximadamente 380-450 bytes com chaves repetidas em cada item. Em Protobuf, o mesmo dado ocupa tipicamente 80-120 bytes — redução de 70-80%. Em sistemas com milhões de chamadas por segundo, isso se traduz em economia significativa de bandwidth e CPU de serialização/deserialização.

nota Compressão e Protobuf: JSON comprimido com gzip frequentemente rivaliza com Protobuf não comprimido em tamanho. A vantagem real do Protobuf sobre JSON comprimido está na velocidade de serialização (benchmarks mostram 5-10x mais rápido) e na verificação de tipos em compile time — não apenas no tamanho final.

Campos desconhecidos e compatibilidade

Uma propriedade fundamental do encoding é que clientes que não conhecem um field number simplesmente preservam os bytes desconhecidos ao reserializar. Isso viabiliza compatibilidade bidirecional: novos clientes podem enviar campos que servidores antigos ignoram, e servidores novos podem retornar campos que clientes antigos passam adiante sem interpretar.

Geração de código e toolchain

O compilador protoc processa arquivos .proto e gera código para a linguagem alvo via plugins. O fluxo é: escrever .proto → executar protoc com plugins → código gerado vai para controle de versão ou build pipeline.

C# com grpc-dotnet

# Adicionar ao .csproj — protoc via Grpc.Tools
<PackageReference Include="Google.Protobuf" Version="3.*" />
<PackageReference Include="Grpc.Tools" Version="2.*" PrivateAssets="All" />
<PackageReference Include="Grpc.Net.Client" Version="2.*" />
<PackageReference Include="Grpc.AspNetCore" Version="2.*" />

<!-- No .csproj, declarar os .proto files -->
<ItemGroup>
  <Protobuf Include="Protos\orders.v1.proto" GrpcServices="Server" />
</ItemGroup>

<!-- Grpc.Tools roda protoc automaticamente no build -->

O MSBuild com Grpc.Tools gera automaticamente as classes OrderServiceBase (para implementar no servidor) e OrderServiceClient (para usar no cliente). O servidor herda de OrderServiceBase e sobrescreve os métodos que deseja expor.

Python com grpcio-tools

# Instalação
pip install grpcio grpcio-tools

# Geração de código
python -m grpc_tools.protoc \
  --proto_path=protos \
  --python_out=gen \
  --pyi_out=gen \
  --grpc_python_out=gen \
  protos/orders/v1/orders.proto

# Arquivos gerados:
# gen/orders/v1/orders_pb2.py        — classes de mensagens
# gen/orders/v1/orders_pb2.pyi       — type stubs para mypy
# gen/orders/v1/orders_pb2_grpc.py   — stubs de servidor e cliente

Go com protoc-gen-go

# Instalação dos plugins
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

# Geração de código
protoc \
  --go_out=. \
  --go_opt=paths=source_relative \
  --go-grpc_out=. \
  --go-grpc_opt=paths=source_relative \
  orders/v1/orders.proto

# Arquivos gerados:
# orders/v1/orders.pb.go      — structs de mensagens
# orders/v1/orders_grpc.pb.go — interfaces de servidor e cliente
dica Buf como alternativa ao protoc: a ferramenta Buf simplifica o toolchain com um único arquivo buf.yaml, lint automático de breaking changes, e BSR (Buf Schema Registry) para compartilhar schemas entre times. Em projetos com múltiplos serviços gRPC, Buf reduz significativamente o atrito de manter versões de protoc e plugins sincronizadas.

Estratégia de repositório de schemas

Existem duas estratégias para organizar arquivos .proto:

Quatro modos de streaming

O HTTP/2 como transporte permite que gRPC implemente quatro padrões distintos de comunicação. Escolher o padrão errado não apenas afeta performance — pode tornar a API semanticamente incorreta.

1. Unary RPC

O cliente envia uma requisição, o servidor processa e responde com uma mensagem. É o padrão mais simples e o mais comum. Equivale funcionalmente a uma chamada REST POST ou GET. O overhead é uma negociação de stream HTTP/2, headers de metadata e trailers de status.

// .proto
rpc CreateOrder(CreateOrderRequest) returns (CreateOrderResponse);

// Quando usar:
// - Operações CRUD simples
// - Queries que retornam um único resultado
// - Comandos com resposta de confirmação
// - Sempre que não há necessidade de streaming

2. Server Streaming RPC

O cliente envia uma requisição, o servidor responde com um stream de mensagens. O cliente lê até o servidor fechar o stream ou cancelar. O servidor pode enviar mensagens à medida que os dados ficam disponíveis — sem precisar acumular tudo na memória.

// .proto
rpc WatchOrder(WatchOrderRequest) returns (stream OrderEvent);
rpc ListOrdersByCustomer(ListOrdersByCustomerRequest) returns (stream Order);

// Casos de uso:
// - Notificações em tempo real (push de eventos)
// - Resultados de queries grandes (evita carregar tudo na memória)
// - Download de arquivos ou datasets
// - Log tailing
// - Dashboards com atualizações contínuas

Server streaming com gRPC é fundamentalmente diferente de Server-Sent Events (SSE): o protocolo é bidirecional em nível de transporte (HTTP/2), o schema é tipado, e o backpressure é gerenciado automaticamente pelo fluxo de controle do HTTP/2.

3. Client Streaming RPC

O cliente envia um stream de mensagens, o servidor processa e responde com uma única mensagem ao final. Útil para uploads em chunks, ingestão de dados em batch, e casos onde o cliente gera dados mais rápido do que o servidor processa — o fluxo de controle do HTTP/2 aplica backpressure automaticamente.

// .proto
rpc BulkCreateOrders(stream CreateOrderRequest) returns (BulkCreateOrdersResponse);
rpc UploadCsv(stream CsvChunk) returns (UploadResult);

// Casos de uso:
// - Upload de arquivos grandes em chunks
// - Ingestão de eventos em batch
// - Cálculos agregados sobre streams de entrada
// - ETL de grandes volumes de dados

4. Bidirectional Streaming RPC

Ambos os lados enviam e recebem streams de mensagens independentemente. O servidor não precisa esperar o cliente terminar para responder, e vice-versa. O protocolo de troca é definido pela aplicação — não pelo framework.

// .proto
rpc SyncOrderStatus(stream WatchOrderRequest) returns (stream OrderEvent);
rpc Chat(stream ChatMessage) returns (stream ChatMessage);

// Casos de uso:
// - Aplicações de chat
// - Colaboração em tempo real
// - Games multiplayer
// - Telemetria bidirecional (envio de comandos + recebimento de métricas)
// - Streaming de AI (tokens em stream com feedback do cliente)
atenção Complexidade operacional do streaming bidirecional: BidiStreaming aumenta substancialmente a complexidade de implementação, testes e debugging. Gerenciamento de ciclo de vida do stream, tratamento de cancelamento de ambos os lados, backpressure e reconexão precisam ser implementados explicitamente. Use apenas quando os outros três modos são insuficientes para o caso de uso.

HTTP/2 e multiplexing

Um detalhe fundamental: múltiplos streams gRPC compartilham uma única conexão TCP via multiplexing HTTP/2. Não há head-of-line blocking a nível de aplicação (o HOL blocking do TCP ainda existe, mas é menos frequente). Em sistemas com alta taxa de requisições, isso elimina o overhead de criar novas conexões TCP para cada RPC — um problema real em REST com HTTP/1.1.

Modelo de erro gRPC

gRPC define um modelo de erro estruturado baseado em status codes — diferente de HTTP que usa códigos no cabeçalho de resposta. O status é retornado nos trailers HTTP/2 ao final do stream, acompanhado de uma mensagem de texto e opcionalmente detalhes ricos.

Status codes

CodeValorSemânticaRetriável?
OK0Sucesso
CANCELLED1Cancelado pelo clienteNão
UNKNOWN2Erro desconhecido (fallback)Sim
INVALID_ARGUMENT3Argumento inválido do clienteNão
DEADLINE_EXCEEDED4Deadline expirouSim (com backoff)
NOT_FOUND5Entidade não encontradaNão
ALREADY_EXISTS6Tentativa de criar algo existenteNão
PERMISSION_DENIED7Sem permissão para a operaçãoNão
RESOURCE_EXHAUSTED8Rate limit, quota excedidaSim (com backoff)
FAILED_PRECONDITION9Estado inválido para a operaçãoNão
ABORTED10Conflito de concorrência (optimistic lock)Sim
OUT_OF_RANGE11Fora do intervalo válidoNão
UNIMPLEMENTED12Operação não implementadaNão
INTERNAL13Erro interno do servidorSim
UNAVAILABLE14Servidor temporariamente indisponívelSim
DATA_LOSS15Perda de dados irrecuperávelNão
UNAUTHENTICATED16Sem credenciais válidasNão

Rich error details

O status code sozinho raramente é suficiente para diagnóstico. A Google API Improvement Proposals (AIP) define um modelo de erros ricos via google.rpc.Status que embute mensagens Any com detalhes estruturados:

// google/rpc/status.proto (incluído via googleapis)
message Status {
  int32 code = 1;        // StatusCode como int
  string message = 2;   // Mensagem legível por humanos
  repeated google.protobuf.Any details = 3; // Detalhes ricos
}

// Tipos de detalhe disponíveis:
// google.rpc.ErrorInfo    — reason, domain, metadata
// google.rpc.RetryInfo    — retry_delay para quando tentar novamente
// google.rpc.DebugInfo    — stack_entries, detail
// google.rpc.QuotaFailure — violations com quota_id e description
// google.rpc.BadRequest   — field_violations com field e description
// google.rpc.PreconditionFailure — violations com type, subject, description
// C# — retornando erro rico
using Google.Rpc;
using Grpc.Core;

// No handler do servidor
var status = new Google.Rpc.Status
{
    Code = (int)StatusCode.InvalidArgument,
    Message = "Dados de pedido inválidos",
    Details =
    {
        Any.Pack(new BadRequest
        {
            FieldViolations =
            {
                new BadRequest.Types.FieldViolation
                {
                    Field = "items",
                    Description = "Lista de itens não pode estar vazia"
                },
                new BadRequest.Types.FieldViolation
                {
                    Field = "customer_id",
                    Description = "ID de cliente é obrigatório"
                }
            }
        })
    }
};

throw status.ToRpcException();

Mapeamento gRPC ↔ HTTP para transcoding

Infraestruturas como Envoy e gRPC-Gateway suportam transcoding — exposição de serviços gRPC como REST automaticamente. Para isso, o .proto usa anotações google.api.http:

import "google/api/annotations.proto";

service OrderService {
  rpc CreateOrder(CreateOrderRequest) returns (CreateOrderResponse) {
    option (google.api.http) = {
      post: "/v1/orders"
      body: "*"
    };
  }
  rpc GetOrder(GetOrderRequest) returns (Order) {
    option (google.api.http) = {
      get: "/v1/orders/{id}"
    };
  }
}

Interceptors — middleware gRPC

Interceptors são o equivalente de middleware em gRPC — permitem adicionar comportamento transversal sem poluir a lógica de negócio: autenticação, logging, tracing, métricas, retry, circuit breaker. O padrão é Chain of Responsibility: cada interceptor chama o próximo na cadeia.

Interceptors no servidor (C#)

// Interceptor de logging e tracing
public class ObservabilityInterceptor : Interceptor
{
    private readonly ILogger<ObservabilityInterceptor> _logger;
    private readonly ActivitySource _activitySource;

    public ObservabilityInterceptor(
        ILogger<ObservabilityInterceptor> logger,
        ActivitySource activitySource)
    {
        _logger = logger;
        _activitySource = activitySource;
    }

    // Intercepta Unary RPCs
    public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
        TRequest request,
        ServerCallContext context,
        UnaryServerMethod<TRequest, TResponse> continuation)
    {
        var method = context.Method; // "/orders.v1.OrderService/CreateOrder"
        var peer = context.Peer;
        var deadline = context.Deadline;

        using var activity = _activitySource.StartActivity(
            method,
            ActivityKind.Server);

        activity?.SetTag("rpc.system", "grpc");
        activity?.SetTag("rpc.method", method);
        activity?.SetTag("grpc.peer", peer);

        var sw = Stopwatch.StartNew();

        try
        {
            var response = await continuation(request, context);

            _logger.LogInformation(
                "gRPC {Method} OK in {ElapsedMs}ms",
                method, sw.ElapsedMilliseconds);

            GrpcMetrics.RecordDuration(method, "OK", sw.Elapsed);
            return response;
        }
        catch (RpcException ex)
        {
            _logger.LogWarning(
                "gRPC {Method} {Status} in {ElapsedMs}ms: {Message}",
                method, ex.StatusCode, sw.ElapsedMilliseconds, ex.Message);

            activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
            GrpcMetrics.RecordDuration(method, ex.StatusCode.ToString(), sw.Elapsed);
            throw;
        }
    }

    // Intercepta Server Streaming
    public override async Task ServerStreamingServerHandler<TRequest, TResponse>(
        TRequest request,
        IServerStreamWriter<TResponse> responseStream,
        ServerCallContext context,
        ServerStreamingServerMethod<TRequest, TResponse> continuation)
    {
        using var activity = _activitySource.StartActivity(context.Method, ActivityKind.Server);
        await continuation(request, responseStream, context);
    }
}

// Registro no Program.cs
builder.Services.AddGrpc(options =>
{
    options.Interceptors.Add<AuthInterceptor>();      // primeiro: auth
    options.Interceptors.Add<ObservabilityInterceptor>(); // depois: obs
    options.Interceptors.Add<ValidationInterceptor>();    // por último: validação
});

Interceptors no cliente — retry automático

// Interceptor de retry com backoff exponencial (cliente C#)
public class RetryInterceptor : Interceptor
{
    private static readonly HashSet<StatusCode> RetriableStatuses = new()
    {
        StatusCode.Unavailable,
        StatusCode.ResourceExhausted,
        StatusCode.Internal,
        StatusCode.DeadlineExceeded,
    };

    public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
        TRequest request,
        ClientInterceptorContext<TRequest, TResponse> context,
        AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
    {
        // Delegar para lógica async com retry
        return new AsyncUnaryCall<TResponse>(
            ExecuteWithRetry(request, context, continuation),
            // ... headers, status, trailers, dispose
        );
    }

    private async Task<TResponse> ExecuteWithRetry<TRequest, TResponse>(
        TRequest request,
        ClientInterceptorContext<TRequest, TResponse> context,
        AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
        where TRequest : class
        where TResponse : class
    {
        var attempt = 0;
        while (true)
        {
            try
            {
                var call = continuation(request, context);
                return await call.ResponseAsync;
            }
            catch (RpcException ex) when (RetriableStatuses.Contains(ex.StatusCode) && attempt < 3)
            {
                attempt++;
                var delay = TimeSpan.FromMilliseconds(Math.Pow(2, attempt) * 100);
                await Task.Delay(delay);
            }
        }
    }
}
nota gRPC built-in retry policy: o gRPC tem suporte nativo a retry policy configurável via ServiceConfig, evitando a necessidade de interceptors customizados para o caso simples. A configuração define maxAttempts, initialBackoff, maxBackoff, backoffMultiplier e retryableStatusCodes. O retry nativo aplica backoff exponencial com jitter automaticamente.

Deadlines e cancelamento

Deadlines são uma das funcionalidades mais importantes do gRPC e das mais frequentemente ignoradas. Um deadline é um ponto absoluto no tempo após o qual a operação deve ser considerada falha — diferente de timeout, que é relativo ao início. A distinção importa: em chamadas em cadeia, o deadline original é propagado entre serviços, preservando o orçamento de tempo global da requisição.

Propagação de deadline em cadeia de serviços

// Serviço A chama Serviço B com o mesmo contexto (deadline propagado)
public override async Task<CreateOrderResponse> CreateOrder(
    CreateOrderRequest request,
    ServerCallContext context)
{
    // context.CancellationToken é cancelado quando o deadline expira
    // ou quando o cliente cancela a requisição

    // CORRETO: passar o token de cancelamento
    var customer = await _customerService.GetCustomerAsync(
        request.CustomerId,
        context.CancellationToken);  // ← deadline propagado

    // ERRADO: criar novo CancellationToken sem deadline
    var customer = await _customerService.GetCustomerAsync(
        request.CustomerId,
        CancellationToken.None);  // ← perde o deadline!

    // Para chamadas gRPC saindo deste serviço:
    // O deadline do contexto de entrada é propagado automaticamente
    // se você usar o mesmo CancellationToken no CallOptions de saída
    var inventoryCall = _inventoryClient.CheckStockAsync(
        new CheckStockRequest { ProductIds = { productIds } },
        new CallOptions(
            deadline: context.Deadline,  // mantém o deadline original
            cancellationToken: context.CancellationToken));

    return new CreateOrderResponse { Order = order };
}

Tratando DEADLINE_EXCEEDED vs CANCELLED

A distinção entre os dois status é crucial para operação:

atenção DEADLINE_EXCEEDED e idempotência: ao receber DEADLINE_EXCEEDED em um CreateOrder, o cliente não sabe se o pedido foi criado ou não. Isso exige idempotency keys para retry seguro — um padrão que exploramos no conceito 11 (Idempotência e at-least-once delivery). gRPC não resolve esse problema automaticamente; é responsabilidade do design da API.

Reflection e grpcurl

gRPC Server Reflection é um protocolo que permite que clientes descubram os serviços disponíveis e seus schemas sem acesso ao arquivo .proto. É o equivalente do Swagger UI para gRPC — fundamental para debugging interativo em desenvolvimento e staging.

Habilitando reflection

// C# — Program.cs
builder.Services.AddGrpcReflection();
// ...
if (app.Environment.IsDevelopment())
{
    app.MapGrpcReflectionService();
}

// Go — importar o pacote de reflection
import _ "google.golang.org/grpc/reflection"
// ...
reflection.Register(grpcServer) // registra no servidor gRPC

# Python
pip install grpcio-reflection
from grpc_reflection.v1alpha import reflection
SERVICE_NAMES = (
    orders_pb2.DESCRIPTOR.services_by_name['OrderService'].full_name,
    reflection.SERVICE_NAME,
)
reflection.enable_server_reflection(SERVICE_NAMES, server)

grpcurl — curl para gRPC

# Listar serviços disponíveis
grpcurl -plaintext localhost:5000 list

# Descrever um serviço específico
grpcurl -plaintext localhost:5000 describe orders.v1.OrderService

# Descrever uma mensagem
grpcurl -plaintext localhost:5000 describe orders.v1.Order

# Chamar um RPC unary com JSON
grpcurl -plaintext \
  -d '{"customer_id": "cust-123", "items": [{"product_id": "prod-1", "quantity": 2, "unit_price_cents": 1000}]}' \
  localhost:5000 \
  orders.v1.OrderService/CreateOrder

# Chamar com headers (metadata gRPC)
grpcurl -plaintext \
  -H "Authorization: Bearer eyJhbG..." \
  -d '{"id": "order-abc"}' \
  localhost:5000 \
  orders.v1.OrderService/GetOrder

# Server streaming — exibe mensagens conforme chegam
grpcurl -plaintext \
  -d '{"order_id": "order-abc"}' \
  localhost:5000 \
  orders.v1.OrderService/WatchOrder

Outras ferramentas do ecossistema

gRPC-Web — bridging para browsers

gRPC nativo requer HTTP/2 com acesso de baixo nível às trailers — algo que os browsers não expõem via fetch/XMLHttpRequest. gRPC-Web é uma especificação alternativa que usa uma camada de tradução entre o browser e o servidor, permitindo que frontends se comuniquem com serviços gRPC.

Arquitetura com proxy

Browser
  ↓ gRPC-Web (HTTP/1.1 ou HTTP/2 + base64 encoding de trailers)
Envoy Proxy / nginx com módulo grpc-web
  ↓ gRPC nativo (HTTP/2 com trailers reais)
Backend gRPC Server

Limitações do gRPC-Web

gRPC-Web suporta apenas unary RPC e server streaming. Client streaming e bidirectional streaming não são suportados — para esses casos, use WebSockets ou SSE. Além disso, o encoding de trailers varia entre implementações.

Connect: alternativa moderna

O protocolo Connect (da Buf) resolve limitações do gRPC-Web sendo nativo ao browser, compatível com gRPC e gRPC-Web, e usando JSON legível por padrão (com Protobuf como opção). Um único servidor Connect responde corretamente a clientes gRPC nativos, gRPC-Web e clientes HTTP simples com curl — sem proxy.

Quando usar gRPC — e quando não usar

gRPC vence quando:

gRPC perde quando:

dica Regra prática: use gRPC para comunicação service-to-service dentro do cluster (malha interna), e REST para APIs expostas externamente (ingress). Você pode ter ambos simultaneamente: os serviços se comunicam via gRPC internamente e expõem REST via transcoding (Envoy, gRPC-Gateway) ou via uma camada de API Gateway separada.

Comparação por linguagem

Implementação de um serviço OrderService com unary e server streaming em cada linguagem, mostrando as idiomáticas de cada ecossistema.

C# — ASP.NET Core + grpc-dotnet
// OrderService.cs
public class OrderServiceImpl : OrderService.OrderServiceBase
{
    private readonly IOrderRepository _repo;
    private readonly ILogger<OrderServiceImpl> _logger;

    public OrderServiceImpl(IOrderRepository repo, ILogger<OrderServiceImpl> logger)
    {
        _repo = repo;
        _logger = logger;
    }

    // Unary RPC
    public override async Task<CreateOrderResponse> CreateOrder(
        CreateOrderRequest request,
        ServerCallContext context)
    {
        if (string.IsNullOrEmpty(request.CustomerId))
            throw new RpcException(new Status(
                StatusCode.InvalidArgument, "customer_id é obrigatório"));

        if (request.Items.Count == 0)
            throw new RpcException(new Status(
                StatusCode.InvalidArgument, "items não pode estar vazio"));

        var order = await _repo.CreateAsync(
            new OrderDomain
            {
                CustomerId = request.CustomerId,
                Items = request.Items.Select(i => new OrderItemDomain
                {
                    ProductId = i.ProductId,
                    Quantity = i.Quantity,
                    UnitPriceCents = i.UnitPriceCents,
                }).ToList(),
                Metadata = request.Metadata,
            },
            context.CancellationToken);

        return new CreateOrderResponse { Order = order.ToProto() };
    }

    // Server Streaming RPC
    public override async Task WatchOrder(
        WatchOrderRequest request,
        IServerStreamWriter<OrderEvent> responseStream,
        ServerCallContext context)
    {
        await foreach (var domainEvent in _repo.WatchOrderEventsAsync(
            request.OrderId,
            context.CancellationToken))
        {
            // Verificar cancelamento antes de escrever
            if (context.CancellationToken.IsCancellationRequested)
                break;

            await responseStream.WriteAsync(new OrderEvent
            {
                OrderId = domainEvent.OrderId,
                PreviousStatus = domainEvent.PreviousStatus.ToProto(),
                NewStatus = domainEvent.NewStatus.ToProto(),
                OccurredAt = Timestamp.FromDateTime(domainEvent.OccurredAt),
            });
        }
    }
}

// Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGrpc(opts =>
{
    opts.EnableDetailedErrors = builder.Environment.IsDevelopment();
    opts.Interceptors.Add<ObservabilityInterceptor>();
});
builder.Services.AddGrpcReflection();
builder.Services.AddScoped<IOrderRepository, OrderRepository>();

var app = builder.Build();
app.MapGrpcService<OrderServiceImpl>();
if (app.Environment.IsDevelopment())
    app.MapGrpcReflectionService();
app.Run();

// Cliente (ex: em outro serviço)
var channel = GrpcChannel.ForAddress("https://orders-service:5001",
    new GrpcChannelOptions
    {
        ServiceConfig = new ServiceConfig
        {
            MethodConfigs =
            {
                new MethodConfig
                {
                    Names = { MethodName.Default },
                    RetryPolicy = new RetryPolicy
                    {
                        MaxAttempts = 3,
                        InitialBackoff = TimeSpan.FromMilliseconds(100),
                        MaxBackoff = TimeSpan.FromSeconds(2),
                        BackoffMultiplier = 2,
                        RetryableStatusCodes = { StatusCode.Unavailable }
                    }
                }
            }
        }
    });

var client = new OrderService.OrderServiceClient(channel);

// Unary com deadline
var response = await client.CreateOrderAsync(
    new CreateOrderRequest
    {
        CustomerId = "cust-123",
        Items = { new OrderItem { ProductId = "prod-1", Quantity = 2, UnitPriceCents = 1000 } }
    },
    deadline: DateTime.UtcNow.AddSeconds(5));

// Server streaming
using var streamingCall = client.WatchOrder(
    new WatchOrderRequest { OrderId = response.Order.Id },
    deadline: DateTime.UtcNow.AddMinutes(30));

await foreach (var evt in streamingCall.ResponseStream.ReadAllAsync())
{
    Console.WriteLine($"Status: {evt.PreviousStatus} → {evt.NewStatus}");
}

C# usa herança de OrderServiceBase gerado, IServerStreamWriter<T> para streaming, e await foreach no cliente para consumir o stream com cancelamento nativo.

Python — grpcio + asyncio
# server.py
import asyncio
import logging
from grpc import aio
import orders.v1.orders_pb2 as orders_pb2
import orders.v1.orders_pb2_grpc as orders_pb2_grpc
from google.protobuf.timestamp_pb2 import Timestamp
from datetime import datetime, timezone

class OrderServiceServicer(orders_pb2_grpc.OrderServiceServicer):

    def __init__(self, repo: OrderRepository):
        self._repo = repo

    async def CreateOrder(self, request, context):
        # Validação
        if not request.customer_id:
            await context.abort(
                grpc.StatusCode.INVALID_ARGUMENT,
                "customer_id é obrigatório"
            )

        if not request.items:
            await context.abort(
                grpc.StatusCode.INVALID_ARGUMENT,
                "items não pode estar vazio"
            )

        try:
            order = await self._repo.create(
                customer_id=request.customer_id,
                items=[
                    {"product_id": i.product_id,
                     "quantity": i.quantity,
                     "unit_price_cents": i.unit_price_cents}
                    for i in request.items
                ],
                metadata=dict(request.metadata),
            )
        except OrderAlreadyExistsError as e:
            await context.abort(grpc.StatusCode.ALREADY_EXISTS, str(e))

        return orders_pb2.CreateOrderResponse(order=order_to_proto(order))

    async def WatchOrder(self, request, context):
        """Server streaming — yielda eventos conforme chegam."""
        async for event in self._repo.watch_order_events(request.order_id):
            # Verificar se cliente ainda está conectado
            if context.cancelled():
                return

            ts = Timestamp()
            ts.FromDatetime(event.occurred_at)

            yield orders_pb2.OrderEvent(
                order_id=event.order_id,
                previous_status=event.previous_status,
                new_status=event.new_status,
                occurred_at=ts,
            )

async def serve():
    server = aio.server(
        interceptors=[ObservabilityInterceptor(), AuthInterceptor()]
    )
    orders_pb2_grpc.add_OrderServiceServicer_to_server(
        OrderServiceServicer(OrderRepository()), server
    )

    # Reflection para desenvolvimento
    from grpc_reflection.v1alpha import reflection
    reflection.enable_server_reflection(
        [orders_pb2.DESCRIPTOR.services_by_name["OrderService"].full_name,
         reflection.SERVICE_NAME],
        server
    )

    server.add_insecure_port("[::]:50051")
    await server.start()
    logging.info("Servidor gRPC escutando em :50051")
    await server.wait_for_termination()

# client.py
import grpc
from grpc import aio
import orders.v1.orders_pb2 as orders_pb2
import orders.v1.orders_pb2_grpc as orders_pb2_grpc

async def main():
    async with aio.insecure_channel("localhost:50051") as channel:
        stub = orders_pb2_grpc.OrderServiceStub(channel)

        # Unary com deadline (em segundos a partir de agora)
        response = await stub.CreateOrder(
            orders_pb2.CreateOrderRequest(
                customer_id="cust-123",
                items=[orders_pb2.OrderItem(
                    product_id="prod-1",
                    quantity=2,
                    unit_price_cents=1000
                )]
            ),
            timeout=5.0  # deadline relativo
        )
        print(f"Pedido criado: {response.order.id}")

        # Server streaming
        async for event in stub.WatchOrder(
            orders_pb2.WatchOrderRequest(order_id=response.order.id),
            timeout=1800.0
        ):
            print(f"Evento: {event.previous_status} → {event.new_status}")

Python usa async for + yield para streaming server-side — o servicer é um gerador assíncrono. O cliente também itera com async for no stub.

Go — google.golang.org/grpc
// server/order_service.go
package server

import (
    "context"
    "time"

    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/status"
    "google.golang.org/protobuf/types/known/timestamppb"

    ordersv1 "github.com/empresa/orders/gen/orders/v1"
)

type OrderServiceServer struct {
    ordersv1.UnimplementedOrderServiceServer // embed para forward compat
    repo OrderRepository
}

func NewOrderServiceServer(repo OrderRepository) *OrderServiceServer {
    return &OrderServiceServer{repo: repo}
}

// CreateOrder — Unary RPC
func (s *OrderServiceServer) CreateOrder(
    ctx context.Context,
    req *ordersv1.CreateOrderRequest,
) (*ordersv1.CreateOrderResponse, error) {
    if req.CustomerId == "" {
        return nil, status.Error(codes.InvalidArgument, "customer_id é obrigatório")
    }
    if len(req.Items) == 0 {
        return nil, status.Error(codes.InvalidArgument, "items não pode estar vazio")
    }

    // Verificar deadline antes de operação custosa
    if deadline, ok := ctx.Deadline(); ok && time.Until(deadline) < 50*time.Millisecond {
        return nil, status.Error(codes.DeadlineExceeded, "tempo insuficiente para processar")
    }

    order, err := s.repo.Create(ctx, &OrderDomain{
        CustomerID: req.CustomerId,
        Items:      protoItemsToDomain(req.Items),
        Metadata:   req.Metadata,
    })
    if err != nil {
        switch {
        case errors.Is(err, ErrAlreadyExists):
            return nil, status.Error(codes.AlreadyExists, err.Error())
        case errors.Is(err, ErrNotFound):
            return nil, status.Error(codes.NotFound, err.Error())
        default:
            return nil, status.Errorf(codes.Internal, "erro interno: %v", err)
        }
    }

    return &ordersv1.CreateOrderResponse{Order: orderToProto(order)}, nil
}

// WatchOrder — Server Streaming RPC
func (s *OrderServiceServer) WatchOrder(
    req *ordersv1.WatchOrderRequest,
    stream ordersv1.OrderService_WatchOrderServer,
) error {
    eventCh, err := s.repo.WatchOrderEvents(stream.Context(), req.OrderId)
    if err != nil {
        return status.Errorf(codes.NotFound, "pedido %s não encontrado", req.OrderId)
    }

    for {
        select {
        case <-stream.Context().Done():
            // Cliente cancelou ou deadline expirou
            return nil
        case event, ok := <-eventCh:
            if !ok {
                return nil // canal fechado, stream completo
            }
            if err := stream.Send(&ordersv1.OrderEvent{
                OrderId:        event.OrderID,
                PreviousStatus: domainStatusToProto(event.PreviousStatus),
                NewStatus:      domainStatusToProto(event.NewStatus),
                OccurredAt:     timestamppb.New(event.OccurredAt),
            }); err != nil {
                return err // erro ao enviar — cliente desconectado
            }
        }
    }
}

// main.go — inicialização do servidor
func main() {
    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
        log.Fatalf("falha ao escutar: %v", err)
    }

    grpcServer := grpc.NewServer(
        grpc.ChainUnaryInterceptor(
            otelgrpc.UnaryServerInterceptor(),
            authInterceptor,
            loggingInterceptor,
        ),
        grpc.ChainStreamInterceptor(
            otelgrpc.StreamServerInterceptor(),
        ),
    )

    ordersv1.RegisterOrderServiceServer(grpcServer, NewOrderServiceServer(repo))
    reflection.Register(grpcServer)

    log.Printf("Servidor gRPC escutando em :50051")
    if err := grpcServer.Serve(lis); err != nil {
        log.Fatalf("falha ao servir: %v", err)
    }
}

// client.go
func NewOrderClient(addr string) (ordersv1.OrderServiceClient, func(), error) {
    conn, err := grpc.NewClient(addr,
        grpc.WithTransportCredentials(insecure.NewCredentials()),
        grpc.WithDefaultServiceConfig(`{
            "methodConfig": [{
                "name": [{}],
                "retryPolicy": {
                    "maxAttempts": 3,
                    "initialBackoff": "0.1s",
                    "maxBackoff": "2s",
                    "backoffMultiplier": 2,
                    "retryableStatusCodes": ["UNAVAILABLE"]
                }
            }]
        }`),
    )
    if err != nil {
        return nil, nil, fmt.Errorf("conectar ao serviço de pedidos: %w", err)
    }

    cleanup := func() { conn.Close() }
    return ordersv1.NewOrderServiceClient(conn), cleanup, nil
}

// uso do cliente — unary + streaming
func example(client ordersv1.OrderServiceClient) {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    resp, err := client.CreateOrder(ctx, &ordersv1.CreateOrderRequest{
        CustomerId: "cust-123",
        Items: []*ordersv1.OrderItem{
            {ProductId: "prod-1", Quantity: 2, UnitPriceCents: 1000},
        },
    })
    if err != nil {
        st := status.Convert(err)
        log.Printf("Erro %s: %s", st.Code(), st.Message())
        return
    }

    // Server streaming com contexto de longa duração
    watchCtx, watchCancel := context.WithTimeout(context.Background(), 30*time.Minute)
    defer watchCancel()

    stream, err := client.WatchOrder(watchCtx,
        &ordersv1.WatchOrderRequest{OrderId: resp.Order.Id})
    if err != nil {
        log.Printf("Erro ao iniciar watch: %v", err)
        return
    }

    for {
        event, err := stream.Recv()
        if err == io.EOF {
            break // stream finalizado pelo servidor
        }
        if err != nil {
            log.Printf("Erro no stream: %v", err)
            break
        }
        log.Printf("Evento: %v → %v", event.PreviousStatus, event.NewStatus)
    }
}

Go usa select com stream.Context().Done() para cancelamento limpo no streaming, e o UnimplementedOrderServiceServer embarcado garante compatibilidade futura quando novos métodos são adicionados ao .proto.

Decisões de engenharia

gRPC vs REST para comunicação interna

Em microsserviços internos onde ambos os lados são controlados pela equipe, gRPC entrega melhor performance (serialização Protobuf ~5x mais rápida que JSON), contratos fortemente tipados com geração de código, e streaming nativo. REST faz sentido quando os consumidores são externos, precisam de cacheabilidade via CDN, ou quando o time não tem maturidade para gerenciar o toolchain protoc/buf.

Regra prática: gRPC para service-to-service dentro do cluster; REST para o ingresso externo. Você pode ter os dois simultaneamente: serviços comunicam via gRPC internamente e expõem REST via transcoding (Envoy, gRPC-Gateway) ou API Gateway na borda.

Unary vs Server Streaming — quando fazer o upgrade?

Use server streaming quando: (1) o resultado é grande demais para caber em memória de uma vez; (2) o cliente precisa de atualizações incrementais enquanto o servidor processa; (3) o servidor empurra eventos ao longo do tempo. A complexidade de gerenciar ciclo de vida de stream, reconexão automática e backpressure é substancialmente maior que unary.

Armadilha: streams de longa duração precisam de heartbeat e lógica de reconexão no cliente — o servidor pode reiniciar silenciosamente e o cliente ficará bloqueado esperando mensagens que nunca chegam sem detectar a desconexão.

Onde colocar os arquivos .proto

Duas estratégias: (1) mesmo repositório do serviço — simples para serviços com poucos consumidores, clientes referenciam via git submodule ou copiam o .proto; (2) repositório centralizado (ex: company/api-schemas) com todos os schemas versionados independentemente — obrigatório quando múltiplos times consomem o mesmo contrato.

O Buf Schema Registry (BSR) é o estado da arte: push de schemas versionados, lint automático, breaking change detection e geração de SDKs remotamente sem instalar protoc localmente. Elimina o atrito de sincronizar versões de protoc entre times.

Client-side retry vs service mesh

A retry policy nativa do gRPC (ServiceConfig) e interceptors de retry funcionam bem para casos simples — UNAVAILABLE com backoff exponencial. Em sistemas com dezenas de serviços, gerenciar retry, timeout, circuit breaking e observabilidade em cada cliente gera explosão de configuração. Um service mesh (Istio, Linkerd) consolida isso na infraestrutura: a política de retry é configurada uma vez no mesh, não em cada serviço.

Trade-off: service mesh adiciona latência (~1ms por hop de sidecar proxy) e complexidade operacional de Kubernetes. Vale a partir de ~10–15 serviços. Abaixo disso, ServiceConfig + interceptors são mais simples e mais fáceis de debugar.

Perguntas de entrevista

O que acontece quando um deadline expira no meio de uma cadeia de serviços gRPC?

O deadline gRPC é um ponto absoluto no tempo propagado da requisição original para toda a cadeia. Quando A chama B que chama C, o mesmo deadline absoluto é passado em cada hop — não um timeout relativo novo. Quando expira, qualquer serviço que detectar o vencimento retorna DEADLINE_EXCEEDED e o sinal se propaga: C aborta, B recebe o erro, cancela seu trabalho e retorna DEADLINE_EXCEEDED para A.

O risco crítico: DEADLINE_EXCEEDED não garante que a operação não foi executada. Se C criou um pedido mas expirou antes de responder a B, A não sabe se o pedido existe — retry sem idempotency key pode criar duplicatas. Em C#, a falha mais comum é passar CancellationToken.None para operações downstream em vez do context.CancellationToken — isso causa trabalho desnecessário mesmo após o cliente ter desistido.

Qual a diferença entre FAILED_PRECONDITION, ABORTED e INVALID_ARGUMENT?

Os três indicam erros originados no cliente, mas com semântica e retriabilidade diferentes:

  • INVALID_ARGUMENT: o próprio request é malformado — campo ausente, formato inválido, valor fora do range aceitável. O cliente nunca deve tentar novamente sem alterar o request. Ex: customer_id vazio.
  • FAILED_PRECONDITION: o request é válido mas o sistema não está no estado correto para processá-lo. O cliente precisa corrigir o estado antes de tentar novamente — retry automático não resolve. Ex: tentar entregar um pedido com status CANCELLED.
  • ABORTED: conflito de concorrência transitório — optimistic lock conflict, transação abortada por contenção. É retriável com backoff: o cliente pode tentar novamente e provavelmente terá sucesso.

A distinção operacional: INVALID_ARGUMENT nunca vai funcionar sem mudar o request; FAILED_PRECONDITION vai funcionar quando o estado mudar; ABORTED vai funcionar se tentar de novo.

Como o Protocol Buffers garante backward e forward compatibility?

O encoding binário usa field numbers, não nomes — isso viabiliza compatibilidade bidirecional sem coordenação explícita:

  • Backward compatibility (clientes novos lendo dados antigos): campos adicionados têm valor zero/default quando ausentes nos dados antigos. Um campo discount_cents = 8 adicionado retorna 0 para dados produzidos antes desse campo existir.
  • Forward compatibility (clientes antigos lendo dados novos): campos desconhecidos são preservados intactos ao reserializar — clientes antigos ignoram field numbers que não reconhecem sem corromper os dados.

Operações que quebram compatibilidade: reutilizar um field number com tipo diferente (wire type incompatível causa corrupção silenciosa), renomear um enum value sem reservar o nome antigo, ou remover um campo sem marcar como reserved. A regra de ouro: field numbers são permanentes em produção. Campos removidos devem ser marcados com reserved 3, 5 to 7; reserved "old_field_name"; — protege contra reuso acidental por futuros desenvolvedores.

Por que gRPC não é adequado para APIs públicas, e como resolver isso?

Três barreiras principais:

  • Tooling obrigatório: consumir uma API gRPC exige o arquivo .proto (ou reflection ativo), geração de código, e um cliente gRPC. REST + JSON funciona com qualquer ferramenta HTTP sem setup adicional.
  • Browser: gRPC nativo usa trailers HTTP/2 que browsers não expõem via fetch/XHR. Requer gRPC-Web (com proxy reverso) ou Connect protocol para funcionar no browser.
  • Infraestrutura legada: firewalls e proxies corporativos frequentemente bloqueiam HTTP/2 ou não interpretam frames gRPC corretamente.

Soluções: (1) Transcoding com anotações google.api.http no .proto + Envoy ou gRPC-Gateway que expõem REST automaticamente a partir do serviço gRPC interno; (2) API Gateway que traduz REST→gRPC na borda, mantendo gRPC internamente; (3) Connect protocol (Buf) — um servidor que responde a gRPC nativo, gRPC-Web e REST com JSON sem proxy.

Quando bidirectional streaming é a escolha certa — e quando não é?

Bidirectional streaming é o padrão mais complexo e deve ser a última escolha. Use quando: (1) cliente e servidor precisam trocar mensagens de forma assíncrona e independente, sem um esperar o outro; (2) o protocolo de comunicação é definido pela aplicação com interações não-sequenciais (comandos vs respostas vs notificações independentes); (3) latência ultra-baixa importa e múltiplos unary RPCs em sequência são inaceitavelmente custosos.

Alternativas antes de escolher bidi: se apenas o servidor empurra dados → server streaming; se apenas o cliente envia bulk → client streaming; se as interações são discretas e independentes → múltiplos unary com connection pooling HTTP/2. Cuidados operacionais críticos: streams de longa duração precisam de heartbeat na camada da aplicação, lógica de reconexão com backoff exponencial, e testes que simulam desconexões abruptas do servidor.

Exercícios práticos

  1. Defina um arquivo .proto para um serviço de notificações com unary SendNotification e server streaming SubscribeToNotifications. Inclua enums de canal (EMAIL, SMS, PUSH) com valor 0 UNSPECIFIED, well-known types para timestamps, e campos opcionais para template_id e metadata.
    Critério: enum deve ter valor 0 UNSPECIFIED obrigatório; usar google.protobuf.Timestamp para datas; campos opcionais declarados com optional; serviço com pelo menos 3 RPCs com mix de unary e streaming; campos removidos marcados como reserved.
  2. Implemente um interceptor de autenticação que extrai um Bearer token dos metadados gRPC (context.Metadata), valida contra um serviço de identidade, e injeta o UserId no contexto. Trate UNAUTHENTICATED vs PERMISSION_DENIED corretamente.
    Critério: ausência de token retorna UNAUTHENTICATED; token inválido/expirado retorna UNAUTHENTICATED; token válido mas sem permissão para o método retorna PERMISSION_DENIED; o interceptor deve funcionar tanto para Unary quanto para Streaming RPCs; deve logar o método chamado e o UserId extraído.
  3. Crie um client streaming RPC BulkImportProducts(stream ImportProductRequest) returns (ImportResult) onde o servidor processa em lotes de 500 itens. Implemente backpressure: se a fila interna do servidor tiver mais de 1000 itens pendentes, pause o recebimento.
    Critério: ImportResult deve conter total_processed, total_failed e lista de erros por item (field + reason); cliente deve enviar ao menos 2000 itens para validar o backpressure; falhas em itens individuais não devem abortar o batch inteiro; processar com uma única transação de banco por batch de 500.
  4. Implemente propagação de deadline em um handler CheckoutOrder que chama três serviços downstream (inventory, payment, shipping) sequencialmente. Passe o deadline original para cada chamada e trate DEADLINE_EXCEEDED de forma diferente de erros de negócio.
    Critério: verificar se o deadline restante é suficiente (>50ms) antes de cada chamada downstream; retornar erro descritivo identificando em qual serviço o deadline foi atingido; DEADLINE_EXCEEDED de downstream não deve vazar como INTERNAL — propagar como DEADLINE_EXCEEDED ou UNAVAILABLE com contexto do estágio.
  5. Compare a payload size de uma lista de 100 pedidos (cada um com ao menos 3 itens) serializada como JSON vs Protobuf. Meça tamanho em bytes e tempo médio de serialização e deserialização em 1000 iterações.
    Critério: documentar resultados em tabela com colunas: formato, tamanho (bytes), serialização (µs/op), deserialização (µs/op), tamanho JSON comprimido com gzip (bytes); calcular razão Protobuf/JSON para cada métrica; rodar em pelo menos duas linguagens (ex: Go e Python) e comparar os resultados.

Referências

  1. docs gRPC Core Concepts — gRPC Team · grpc.io/docs/what-is-grpc/core-concepts/ · quatro modos de streaming, canal, stub e arquitetura de transporte HTTP/2
  2. docs Protocol Buffers Language Guide (proto3) — Google · protobuf.dev/programming-guides/proto3/ · referência completa de tipos, numeração de campos, enums, oneof e well-known types
  3. docs Protocol Buffers Encoding — Google · protobuf.dev/programming-guides/encoding/ · wire types, varint encoding, ZigZag, como field numbers e wire types formam o tag binário
  4. docs Buf Documentation — Buf Technologies · buf.build/docs · toolchain moderno: lint, breaking change detection, BSR e geração remota de SDKs sem instalar protoc
  5. padrão Google API Improvement Proposals (AIPs) — Google · aip.dev · padrões para APIs gRPC/REST: modelo de erro rico AIP-193, resource-oriented design, FieldMask para updates parciais
  6. spec gRPC Retry Design — Proposal A6 — gRPC Team · github.com/grpc/proposal/blob/master/A6-client-retries.md · especificação da retry policy nativa com backoff exponencial e retryable status codes
  7. spec Connect Protocol — Buf Technologies · connectrpc.com/docs/protocol · protocolo que serve gRPC, gRPC-Web e REST JSON a partir do mesmo servidor sem proxy
  8. livro gRPC: Up and Running — Hauer, Indrasiri · O'Reilly, 2020 · referência completa: implementação em Go e Java, streaming, segurança e deployment em produção
  9. padrão Semantic Conventions for gRPC — OpenTelemetry · opentelemetry.io/docs/specs/semconv/rpc/grpc/ · atributos padronizados para spans e métricas de RPCs gRPC com OpenTelemetry
  10. livro Designing Data-Intensive Applications — Kleppmann · O'Reilly, 2017 · Cap. 4 · serialização e evolução de schema: protobuf vs Thrift vs Avro, backward/forward compatibility
  11. padrão HTTP/2 — RFC 7540 — IETF, 2015 · protocolo de transporte do gRPC: multiplexing de streams, flow control, compressão HPACK de headers
  12. docs gRPC Authentication Guide — gRPC Team · grpc.io/docs/guides/auth/ · mecanismos de autenticação: TLS mútuo, token-based, channel credentials vs call credentials