gRPC em Go é uma escolha forte quando serviços internos precisam conversar com contratos explícitos, baixa latência e evolução controlada. Em vez de combinar JSON solto, documentação incompleta e tipos reconstruídos em cada aplicação, o time escreve um arquivo .proto, gera código Go e passa a tratar a API como um contrato versionado.
Isso não significa que gRPC substitui REST em todos os lugares. Para APIs públicas, integrações simples e clientes web diretos, HTTP/JSON continua excelente. O gRPC brilha quando o consumidor também é um serviço controlado pelo time, quando a chamada acontece dentro da infraestrutura, quando há muitos campos tipados, quando streaming importa ou quando a empresa quer padronizar comunicação entre microsserviços.
Este guia mostra como pensar em gRPC com Go em produção: onde usar, como organizar protobuf, como gerar código, como configurar deadlines, interceptors, erros, observabilidade e testes. Ele complementa API REST em Go, OpenAPI em Go, microservices em Go e OpenTelemetry em Go.
Quando usar gRPC em Go
Use gRPC quando a comunicação é principalmente serviço para serviço. Exemplos comuns em empresas brasileiras que usam Go: plataforma de pagamentos chamando antifraude, checkout consultando catálogo, API pública delegando para serviço de contas, worker pedindo enriquecimento de dados, sistema de logística consultando roteirização ou backend B2B orquestrando permissões.
Nesses cenários, o contrato importa mais do que a conveniência de abrir a URL no navegador. Protobuf descreve mensagens, campos obrigatórios por semântica, enums, serviços e métodos. O código gerado reduz erro manual e deixa refactors mais seguros. Se você renomeia um campo de forma incompatível, o problema aparece no build ou nos testes, não apenas quando um consumidor quebra em produção.
Evite gRPC quando o consumidor principal é browser sem camada intermediária, quando parceiros externos esperam HTTP/JSON simples, quando a equipe ainda não domina operação básica de serviços ou quando o problema é pequeno demais para justificar geração de código. Uma API REST bem documentada pode ser melhor do que gRPC mal operado.
Estrutura mínima do projeto
Uma organização simples separa contrato, código gerado e implementação:
myapp/
api/
proto/
user/v1/user.proto
gen/
go/user/v1/
cmd/
server/
main.go
internal/
user/
service.go
grpc_handler.go
go.mod
O arquivo .proto deve viver em um caminho estável. Evite nomes genéricos como service.proto na raiz. Prefira domínio e versão: user/v1/user.proto, billing/v1/invoice.proto, payments/v1/payment.proto. A versão no pacote deixa claro que mudanças incompatíveis exigem novo contrato, não apenas alteração silenciosa.
Um contrato básico pode começar assim:
syntax = "proto3";
package user.v1;
option go_package = "github.com/acme/myapp/api/gen/go/user/v1;userv1";
service UserService {
rpc GetUser(GetUserRequest) returns (GetUserResponse);
}
message GetUserRequest {
string id = 1;
}
message GetUserResponse {
string id = 1;
string email = 2;
string name = 3;
bool active = 4;
}
O número de cada campo é parte do contrato. Nunca reutilize tag removida para outro significado. Se um campo deixou de existir, marque como reservado:
message GetUserResponse {
reserved 5;
reserved "legacy_status";
}
Essa disciplina evita bugs difíceis quando versões antigas e novas convivem durante deploy gradual.
Geração de código com protoc
Instale os plugins de Go:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
Depois gere os arquivos:
protoc \
--go_out=api/gen/go --go_opt=paths=source_relative \
--go-grpc_out=api/gen/go --go-grpc_opt=paths=source_relative \
-I api/proto \
api/proto/user/v1/user.proto
Em repositórios maiores, vale usar Buf para formatar, validar lint, detectar mudanças incompatíveis e simplificar geração. O ponto essencial é que geração de código deve rodar no CI. Se alguém altera .proto e esquece os arquivos gerados, o pipeline precisa falhar.
Implementando o servidor
O handler gRPC normalmente adapta o contrato externo para seu serviço interno:
package user
import (
"context"
userv1 "github.com/acme/myapp/api/gen/go/user/v1"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type GRPCServer struct {
userv1.UnimplementedUserServiceServer
users *Service
}
func (s *GRPCServer) GetUser(ctx context.Context, req *userv1.GetUserRequest) (*userv1.GetUserResponse, error) {
if req.Id == "" {
return nil, status.Error(codes.InvalidArgument, "id é obrigatório")
}
user, err := s.users.FindByID(ctx, req.Id)
if err != nil {
if IsNotFound(err) {
return nil, status.Error(codes.NotFound, "usuário não encontrado")
}
return nil, status.Error(codes.Internal, "erro ao buscar usuário")
}
return &userv1.GetUserResponse{
Id: user.ID,
Email: user.Email,
Name: user.Name,
Active: user.Active,
}, nil
}
Não vaze erro interno cru para o consumidor. Mensagens de erro devem ajudar sem expor detalhes sensíveis. Para logs, registre o erro completo no servidor com request_id, método gRPC e duração. Para o cliente, use codes.InvalidArgument, codes.NotFound, codes.PermissionDenied, codes.Unavailable ou codes.Internal conforme o caso.
Deadlines não são opcionais
Uma chamada gRPC sem deadline pode ficar presa mais tempo do que deveria. Em sistemas distribuídos, isso vira cascata: o serviço A espera B, B espera C, C está lento, e de repente todas as goroutines e conexões estão ocupadas.
No cliente, derive contexto com timeout:
ctx, cancel := context.WithTimeout(parentCtx, 800*time.Millisecond)
defer cancel()
resp, err := client.GetUser(ctx, &userv1.GetUserRequest{Id: userID})
No servidor, propague o mesmo contexto para banco, cache e chamadas externas. Isso conecta gRPC ao guia de context.Context em Go: o contexto recebido no método não é enfeite, é o sinal de ciclo de vida daquela operação.
Também configure limites no transporte: tamanho máximo de mensagem, keepalive, TLS, health check e shutdown gracioso. Em Kubernetes, combine readiness probe, liveness probe e drain correto para não derrubar chamadas durante deploy. O guia de graceful shutdown em Go ajuda nessa parte.
Interceptors para autenticação e observabilidade
Interceptors são o equivalente gRPC de middleware. Eles permitem aplicar lógica comum antes e depois dos métodos: autenticação, autorização, logs, métricas, tracing, rate limiting e tratamento padronizado de erro.
Um interceptor de log pode medir duração e registrar método:
func LoggingInterceptor(logger *slog.Logger) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
start := time.Now()
resp, err := handler(ctx, req)
logger.Info("grpc request",
"method", info.FullMethod,
"duration_ms", time.Since(start).Milliseconds(),
"error", err,
)
return resp, err
}
}
Em produção, prefira instrumentação com OpenTelemetry para traces distribuídos. Assim você consegue ver a requisição entrando pela API REST, chamando serviço gRPC, consultando PostgreSQL e publicando evento. Sem tracing, gRPC vira uma sequência de chamadas rápidas que só parecem simples até o primeiro incidente.
Streaming: use com intenção
gRPC suporta unary, server streaming, client streaming e bidirectional streaming. Isso é poderoso, mas não deve ser usado só porque existe. A maioria das APIs internas começa bem com unary: uma requisição, uma resposta.
Server streaming faz sentido para acompanhar progresso, exportar eventos, transmitir lotes grandes sem carregar tudo na memória ou entregar atualizações contínuas. Client streaming pode servir para upload incremental. Bidirectional streaming é útil em protocolos interativos, mas aumenta complexidade de backpressure, cancelamento, testes e observabilidade.
Antes de escolher streaming, pergunte: o consumidor precisa mesmo receber dados aos poucos? Existe limite claro de duração? O que acontece se o cliente desconectar? Como o servidor aplica backpressure? Como métricas contam mensagens enviadas, falhas e stream aberto por tempo demais?
Evolução de contrato sem quebrar consumidores
Protobuf ajuda, mas não salva contrato mal pensado. Boas práticas:
- Adicione campos novos com tags novas; não renomeie significado antigo.
- Não reutilize tags removidas; use
reserved. - Evite mudar tipo de campo já publicado.
- Prefira campos opcionais ou mensagens novas quando a evolução pode confundir consumidores.
- Mantenha testes de compatibilidade para contratos críticos.
- Documente semântica, não apenas tipos.
O arquivo .proto responde “qual campo existe”. Ele não responde sozinho “quando active=false aparece?”, “qual erro indica bloqueio antifraude?” ou “qual deadline recomendado?”. Escreva comentários no contrato e mantenha exemplos de uso.
Testes úteis para gRPC em Go
Teste handler sem subir rede sempre que possível. Instancie o servidor, chame o método diretamente e valide resposta, erro e código:
resp, err := server.GetUser(ctx, &userv1.GetUserRequest{Id: "user-123"})
if err != nil {
t.Fatal(err)
}
if resp.Email != "[email protected]" {
t.Fatalf("email = %q", resp.Email)
}
Para testes de integração, suba um servidor gRPC em porta local ou bufconn, chame pelo client gerado e valide interceptors, metadata, deadlines e serialização real. Se o método consulta banco, combine com Testcontainers em Go para testar schema, queries e comportamento sem depender de banco compartilhado.
Também teste erro. Um método que retorna codes.NotFound em vez de codes.Internal melhora UX do consumidor e reduz ruído operacional. Em times com muitos serviços, erro padronizado é parte do contrato.
gRPC e carreira Go
gRPC aparece em vagas Go backend, plataforma, fintech, pagamentos e infraestrutura porque ele conecta fundamentos importantes: tipos, rede, contratos, observabilidade, deploy e sistemas distribuídos. Saber escrever um servidor gRPC é útil; saber explicar quando não usar gRPC é ainda melhor.
Se você está montando portfólio, crie um projeto pequeno com API REST pública chamando um serviço gRPC interno. Adicione protobuf versionado, logs estruturados, deadline no client, teste de integração e documentação de contrato. Isso conversa diretamente com vagas Go backend, vagas Go por tecnologia e entrevista técnica Go.
Para acompanhar como gRPC, protobuf e backend aparecem em outras stacks brasileiras, compare também vagas e guias no eu.dev.br. A linguagem muda, mas os sinais de maturidade são parecidos: contrato claro, timeout, observabilidade, testes e rollback seguro.
Checklist antes de colocar em produção
Antes de publicar um serviço gRPC em Go, confirme:
- O
.prototem pacote, versão,go_packagee comentários suficientes. - Código gerado participa do CI e não fica divergente.
- Clientes usam deadline explícito.
- Servidor propaga
context.Contextpara banco e chamadas externas. - Erros usam
codescorretos e não vazam detalhe sensível. - Interceptors registram logs, métricas, tracing e autenticação quando necessário.
- Health check, TLS, limites de mensagem e shutdown estão configurados.
- Mudanças incompatíveis exigem nova versão de contrato.
- Testes cobrem caso feliz, erro, deadline e compatibilidade básica.
gRPC em Go é mais do que “REST com protobuf”. Ele é uma forma de tratar comunicação interna como contrato operacional. Quando o time combina protobuf versionado, deadlines, interceptors, observabilidade e testes, o serviço fica mais fácil de evoluir e menos dependente de conhecimento tribal. Esse é o tipo de maturidade que diferencia um exemplo local de um backend Go pronto para produção.