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);
}
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 proto3 | C# | Python | Go | Uso típico |
|---|---|---|---|---|
| string | string | str | string | IDs, nomes, texto UTF-8 |
| int32 | int | int | int32 | Contadores, quantidades |
| int64 | long | int | int64 | Timestamps Unix, preços em centavos |
| uint32/uint64 | uint/ulong | int | uint32/uint64 | Valores sempre positivos |
| bool | bool | bool | bool | Flags |
| bytes | ByteString | bytes | []byte | Dados binários arbitrários |
| double/float | double/float | float | float64/float32 | Evitar para valores monetários |
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:
- Timestamp — ponto no tempo com precisão de nanosegundos (seconds + nanos desde Unix epoch)
- Duration — intervalo de tempo (seconds + nanos)
- Empty — equivalente a
voidcomo parâmetro ou retorno - Any — envelope para qualquer mensagem proto, inclui type URL para desserialização
- Struct — mapa de valores JSON-like quando o schema não é conhecido em compile time
- FieldMask — lista de campos afetados em operações de update parcial (evita semântica ambígua de PATCH)
- Wrappers —
Int32Value,StringValue, etc. para tipos nullable em proto3
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 Type | ID | Usado para |
|---|---|---|
| Varint | 0 | int32, int64, uint32, uint64, sint32, sint64, bool, enum |
| 64-bit | 1 | fixed64, sfixed64, double |
| Length-delimited | 2 | string, bytes, embedded messages, packed repeated fields |
| 32-bit | 5 | fixed32, 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.
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
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:
- Schema-first no mesmo repositório: o
.protofica no repositório do serviço, o código gerado é commitado ou gerado no build. Funciona bem para serviços internos com poucos consumidores. - Repositório centralizado de schemas: todos os
.protoficam em um repositório separado (ex:company/api-schemas), os consumidores o referenciam via BSR ou git submodule. Garante que múltiplos serviços compartilhem o mesmo schema evoluído de forma coordenada.
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)
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
| Code | Valor | Semântica | Retriável? |
|---|---|---|---|
| OK | 0 | Sucesso | — |
| CANCELLED | 1 | Cancelado pelo cliente | Não |
| UNKNOWN | 2 | Erro desconhecido (fallback) | Sim |
| INVALID_ARGUMENT | 3 | Argumento inválido do cliente | Não |
| DEADLINE_EXCEEDED | 4 | Deadline expirou | Sim (com backoff) |
| NOT_FOUND | 5 | Entidade não encontrada | Não |
| ALREADY_EXISTS | 6 | Tentativa de criar algo existente | Não |
| PERMISSION_DENIED | 7 | Sem permissão para a operação | Não |
| RESOURCE_EXHAUSTED | 8 | Rate limit, quota excedida | Sim (com backoff) |
| FAILED_PRECONDITION | 9 | Estado inválido para a operação | Não |
| ABORTED | 10 | Conflito de concorrência (optimistic lock) | Sim |
| OUT_OF_RANGE | 11 | Fora do intervalo válido | Não |
| UNIMPLEMENTED | 12 | Operação não implementada | Não |
| INTERNAL | 13 | Erro interno do servidor | Sim |
| UNAVAILABLE | 14 | Servidor temporariamente indisponível | Sim |
| DATA_LOSS | 15 | Perda de dados irrecuperável | Não |
| UNAUTHENTICATED | 16 | Sem credenciais válidas | Nã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);
}
}
}
}
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:
- DEADLINE_EXCEEDED: o servidor excedeu o tempo — pode ser retriável. O servidor pode ou não ter completado a operação antes de retornar o erro. Para operações não-idempotentes, isso exige cuidado: a operação pode ter sido executada.
- CANCELLED: o cliente cancelou — o cliente não está mais interessado. Não há sentido em retornar resultado. O servidor deve abortar o trabalho em andamento o mais rápido possível ao detectar cancelamento via
CancellationToken.IsCancellationRequested.
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
- Evans: client REPL interativo para gRPC com autocompletion de campos
- Postman: suporte nativo a gRPC desde 2022, incluindo streaming
- gRPC UI: interface web para explorar e chamar serviços gRPC com reflection
- BloomRPC: GUI para macOS/Linux/Windows similar ao Postman para gRPC
- buf curl: alternativa ao grpcurl integrada ao Buf toolchain
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:
- Comunicação interna entre microsserviços — ambos os lados controlados, schema evolui coordenadamente, performance importa
- Performance crítica — serialização Protobuf é 5-10x mais rápida que JSON, overhead de HTTP/2 é menor que HTTP/1.1
- Contratos fortemente tipados — geração de código elimina divergência de schema entre times, breaking changes detectados em compile time
- Streaming real — server streaming, client streaming, bidirecional — REST não tem equivalente nativo eficiente
- Polyglot teams — código gerado garante consistência entre C#, Python, Go, Java, Rust, etc.
- Sistemas com múltiplos clientes internos — SDK gerado é mais ergonômico que HTTP client manual
gRPC perde quando:
- APIs públicas — REST + JSON é universalmente acessível; gRPC requer tooling específico e nem todo cliente suporta HTTP/2
- Browser como cliente direto — gRPC nativo não funciona em browsers sem proxy ou gRPC-Web
- Debugging ad-hoc — curl funciona com REST; grpcurl requer reflection habilitado ou acesso ao .proto
- Caches HTTP — CDNs e proxies cachean GET requests HTTP; gRPC usa POST para tudo e não é cacheable por intermediários padrão
- Times pequenos sem infraestrutura — toolchain mais complexo (protoc, geração de código, BSR) não compensa para 2 serviços
- Firewalls e proxies legados — alguns proxies corporativos bloqueiam ou não interpretam corretamente HTTP/2
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.
// 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.
# 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.
// 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
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.
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.
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.
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_idvazio. - 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 = 8adicionado 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
-
Defina um arquivo
.protopara um serviço de notificações com unarySendNotificatione server streamingSubscribeToNotifications. Inclua enums de canal (EMAIL, SMS, PUSH) com valor 0 UNSPECIFIED, well-known types para timestamps, e campos opcionais paratemplate_idemetadata.
Critério: enum deve ter valor 0 UNSPECIFIED obrigatório; usargoogle.protobuf.Timestamppara datas; campos opcionais declarados comoptional; serviço com pelo menos 3 RPCs com mix de unary e streaming; campos removidos marcados comoreserved. -
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 oUserIdno contexto. TrateUNAUTHENTICATEDvsPERMISSION_DENIEDcorretamente.
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. -
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:ImportResultdeve contertotal_processed,total_failede 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. -
Implemente propagação de deadline em um handler
CheckoutOrderque 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. -
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
- docs gRPC Core Concepts — gRPC Team · grpc.io/docs/what-is-grpc/core-concepts/ ·
- docs Protocol Buffers Language Guide (proto3) — Google · protobuf.dev/programming-guides/proto3/ ·
- docs Protocol Buffers Encoding — Google · protobuf.dev/programming-guides/encoding/ ·
- docs Buf Documentation — Buf Technologies · buf.build/docs ·
- padrão Google API Improvement Proposals (AIPs) — Google · aip.dev ·
- spec gRPC Retry Design — Proposal A6 — gRPC Team · github.com/grpc/proposal/blob/master/A6-client-retries.md ·
- spec Connect Protocol — Buf Technologies · connectrpc.com/docs/protocol ·
- livro gRPC: Up and Running — Hauer, Indrasiri · O'Reilly, 2020 ·
- padrão Semantic Conventions for gRPC — OpenTelemetry · opentelemetry.io/docs/specs/semconv/rpc/grpc/ ·
- livro Designing Data-Intensive Applications — Kleppmann · O'Reilly, 2017 · Cap. 4 ·
- padrão HTTP/2 — RFC 7540 — IETF, 2015 ·
- docs gRPC Authentication Guide — gRPC Team · grpc.io/docs/guides/auth/ ·