<h2>O que é gRPC e Por Que Importa</h2>
<p>gRPC é um framework de chamada de procedimento remoto (RPC) moderno, desenvolvido pelo Google, que permite que aplicações se comuniquem de forma eficiente através da rede. Diferentemente de REST, que usa HTTP/1.1 e serialização JSON, gRPC é construído sobre HTTP/2 e utiliza Protocol Buffers para serialização de dados. Essa combinação resulta em latência menor, melhor utilização de banda e suporte nativo para streaming bidirecional.</p>
<p>Em Go, gRPC é particularmente poderoso porque a linguagem foi projetada para lidar com concorrência de forma simples e elegante. Se você trabalha com microsserviços, APIs internas de alta performance ou sistemas que precisam comunicação em tempo real, gRPC é uma escolha técnica que justifica o investimento em aprendizado. A comunidade Go ao redor de gRPC é ativa, bem documentada e as bibliotecas disponíveis são maduras.</p>
<h2>Protocol Buffers: A Base de Tudo</h2>
<h3>O que São Protocol Buffers</h3>
<p>Protocol Buffers (protobuf) é uma linguagem de serialização independente de linguagem desenvolvida pelo Google. Você define suas estruturas de dados em arquivos <code>.proto</code> e ferramentas geram código para serialização e desserialização em qualquer linguagem. Ao contrário de JSON, os Protocol Buffers são binários, mais compactos e mais rápidos de processar.</p>
<p>Um arquivo <code>.proto</code> é a interface contratual entre cliente e servidor. Quando você modifica um <code>.proto</code>, precisa regenerar o código em ambos os lados. Essa abordagem garante compatibilidade, versionamento claro e documentação automática.</p>
<h3>Estrutura Básica de um Arquivo Proto</h3>
<p>Vamos criar um arquivo <code>user.proto</code> para um serviço simples de gerenciamento de usuários:</p>
<pre><code class="language-protobuf">syntax = "proto3";
package user;
option go_package = "github.com/seu-usuario/seu-projeto/pb/user";
message User {
int32 id = 1;
string name = 2;
string email = 3;
int32 age = 4;
}
message CreateUserRequest {
string name = 1;
string email = 2;
int32 age = 3;
}
message CreateUserResponse {
User user = 1;
string message = 2;
}
service UserService {
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
rpc GetUser(User) returns (User);
}</code></pre>
<p>A declaração <code>syntax = "proto3"</code> define a versão do Protocol Buffers (a mais atual e recomendada). O <code>package</code> organiza seu código gerado, e <code>go_package</code> especifica o caminho Go do pacote gerado. Cada campo tem um número único (<code>= 1</code>, <code>= 2</code>, etc.) que identifica aquele campo de forma binária — nunca reutilize esses números em versões futuras, pois isso quebra compatibilidade.</p>
<h3>Gerando Código Go a Partir do Proto</h3>
<p>Para converter seu arquivo <code>.proto</code> em código Go, você precisa instalar o compilador <code>protoc</code> e plugins Go:</p>
<pre><code class="language-bash"># Instalar protoc (no macOS com Homebrew)
brew install protobuf
Instalar plugins Go para protoc
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
Gerar código Go
protoc --go_out=. --go-grpc_out=. user.proto</code></pre>
<p>Isso criará dois arquivos: <code>user.pb.go</code> (estruturas de dados) e <code>user_grpc.pb.go</code> (interfaces e clientes/servidores gRPC). Nunca edite esses arquivos manualmente — eles são gerados e serão sobrescritos na próxima execução do <code>protoc</code>.</p>
<h2>Implementando um Serviço gRPC Básico</h2>
<h3>Servidor gRPC</h3>
<p>Agora vamos implementar um servidor que satisfaça a interface gerada. Crie um arquivo <code>server.go</code>:</p>
<pre><code class="language-go">package main
import (
"context"
"fmt"
"log"
"net"
pb "github.com/seu-usuario/seu-projeto/pb/user"
"google.golang.org/grpc"
)
type userServer struct {
pb.UnimplementedUserServiceServer
users map[int32]*pb.User
nextID int32
}
func (s userServer) CreateUser(ctx context.Context, req pb.CreateUserRequest) (*pb.CreateUserResponse, error) {
s.nextID++
user := &pb.User{
Id: s.nextID,
Name: req.Name,
Email: req.Email,
Age: req.Age,
}
s.users[user.Id] = user
return &pb.CreateUserResponse{
User: user,
Message: "User created successfully",
}, nil
}
func (s userServer) GetUser(ctx context.Context, req pb.User) (*pb.User, error) {
user, ok := s.users[req.Id]
if !ok {
return nil, fmt.Errorf("user not found")
}
return user, nil
}
func main() {
listener, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("Failed to listen: %v", err)
}
grpcServer := grpc.NewServer()
pb.RegisterUserServiceServer(grpcServer, &userServer{
users: make(map[int32]*pb.User),
nextID: 0,
})
fmt.Println("Server running on :50051")
if err := grpcServer.Serve(listener); err != nil {
log.Fatalf("Failed to serve: %v", err)
}
}</code></pre>
<p>O servidor escuta na porta 50051 (padrão para gRPC). A estrutura <code>userServer</code> implementa as interfaces geradas pelo <code>protoc</code>. Note que herdamos <code>UnimplementedUserServiceServer</code> para garantir compatibilidade futura se novos métodos forem adicionados ao serviço.</p>
<h3>Cliente gRPC</h3>
<p>Crie um arquivo <code>client.go</code> para testar:</p>
<pre><code class="language-go">package main
import (
"context"
"fmt"
"log"
"time"
pb "github.com/seu-usuario/seu-projeto/pb/user"
"google.golang.org/grpc"
)
func main() {
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
if err != nil {
log.Fatalf("Failed to connect: %v", err)
}
defer conn.Close()
client := pb.NewUserServiceClient(conn)
// Criar um usuário
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
createResp, err := client.CreateUser(ctx, &pb.CreateUserRequest{
Name: "João Silva",
Email: "joao@example.com",
Age: 28,
})
if err != nil {
log.Fatalf("CreateUser failed: %v", err)
}
fmt.Printf("Created user: %v\n", createResp.User)
// Buscar um usuário
ctx, cancel = context.WithTimeout(context.Background(), time.Second)
defer cancel()
getResp, err := client.GetUser(ctx, &pb.User{Id: createResp.User.Id})
if err != nil {
log.Fatalf("GetUser failed: %v", err)
}
fmt.Printf("Retrieved user: %v\n", getResp)
}</code></pre>
<p>O cliente cria uma conexão com o servidor usando <code>grpc.Dial</code>. Note o uso de <code>context.WithTimeout</code> para adicionar um deadline: se o servidor não responder em 1 segundo, a requisição é cancelada. Isso é essencial em sistemas distribuídos para evitar travamentos.</p>
<h2>Streaming em gRPC</h2>
<h3>Tipos de Streaming</h3>
<p>gRPC suporta quatro padrões de comunicação: unário (tradicional request-response), server streaming (servidor envia múltiplas mensagens), client streaming (cliente envia múltiplas mensagens) e bidirecional (ambos enviam múltiplas mensagens). Streaming é particularmente útil para dados em tempo real, download/upload de arquivos grandes ou processamento de fluxos de eventos.</p>
<h3>Definindo Streaming no Proto</h3>
<p>Vamos estender nosso arquivo <code>user.proto</code> com streaming:</p>
<pre><code class="language-protobuf">syntax = "proto3";
package user;
option go_package = "github.com/seu-usuario/seu-projeto/pb/user";
message User {
int32 id = 1;
string name = 2;
string email = 3;
int32 age = 4;
}
message CreateUserRequest {
string name = 1;
string email = 2;
int32 age = 3;
}
message CreateUserResponse {
User user = 1;
string message = 2;
}
message ListUsersRequest {
int32 limit = 1;
}
message UserEvent {
User user = 1;
string event_type = 2;
}
service UserService {
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
rpc GetUser(User) returns (User);
rpc ListUsers(ListUsersRequest) returns (stream User);
rpc WatchUsers(stream User) returns (stream UserEvent);
}</code></pre>
<p>A palavra-chave <code>stream</code> define que aquele parâmetro pode ter múltiplas mensagens. <code>ListUsers</code> é server streaming (cliente envia uma requisição, servidor envia múltiplos usuários). <code>WatchUsers</code> é bidirecional (ambos enviam múltiplas mensagens).</p>
<h3>Implementando Server Streaming</h3>
<p>Atualize seu <code>server.go</code>:</p>
<pre><code class="language-go">package main
import (
"context"
"fmt"
"log"
"net"
"time"
pb "github.com/seu-usuario/seu-projeto/pb/user"
"google.golang.org/grpc"
)
type userServer struct {
pb.UnimplementedUserServiceServer
users map[int32]*pb.User
nextID int32
}
func (s userServer) CreateUser(ctx context.Context, req pb.CreateUserRequest) (*pb.CreateUserResponse, error) {
s.nextID++
user := &pb.User{
Id: s.nextID,
Name: req.Name,
Email: req.Email,
Age: req.Age,
}
s.users[user.Id] = user
return &pb.CreateUserResponse{
User: user,
Message: "User created successfully",
}, nil
}
func (s userServer) GetUser(ctx context.Context, req pb.User) (*pb.User, error) {
user, ok := s.users[req.Id]
if !ok {
return nil, fmt.Errorf("user not found")
}
return user, nil
}
func (s userServer) ListUsers(req pb.ListUsersRequest, stream grpc.ServerStream) error {
count := int32(0)
for _, user := range s.users {
if count >= req.Limit && req.Limit > 0 {
break
}
if err := stream.SendMsg(user); err != nil {
return err
}
count++
}
return nil
}
func (s *userServer) WatchUsers(stream grpc.ServerStream) error {
for {
user := &pb.User{}
if err := stream.RecvMsg(user); err != nil {
return err
}
event := &pb.UserEvent{
User: user,
EventType: "user_received",
}
if err := stream.SendMsg(event); err != nil {
return err
}
}
}
func main() {
listener, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("Failed to listen: %v", err)
}
grpcServer := grpc.NewServer()
pb.RegisterUserServiceServer(grpcServer, &userServer{
users: make(map[int32]*pb.User),
nextID: 0,
})
fmt.Println("Server running on :50051")
if err := grpcServer.Serve(listener); err != nil {
log.Fatalf("Failed to serve: %v", err)
}
}</code></pre>
<p>Em <code>ListUsers</code>, usamos <code>stream.SendMsg()</code> para enviar cada usuário. O cliente receberá todos os usuários em sequência. Em <code>WatchUsers</code> (bidirecional), usamos <code>stream.RecvMsg()</code> para receber mensagens do cliente e <code>stream.SendMsg()</code> para responder.</p>
<h3>Cliente com Streaming</h3>
<pre><code class="language-go">package main
import (
"context"
"fmt"
"log"
"time"
pb "github.com/seu-usuario/seu-projeto/pb/user"
"google.golang.org/grpc"
)
func main() {
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
if err != nil {
log.Fatalf("Failed to connect: %v", err)
}
defer conn.Close()
client := pb.NewUserServiceClient(conn)
// Server streaming: ListUsers
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
stream, err := client.ListUsers(ctx, &pb.ListUsersRequest{Limit: 10})
if err != nil {
log.Fatalf("ListUsers failed: %v", err)
}
for {
user, err := stream.Recv()
if err != nil {
fmt.Println("Stream finished")
break
}
fmt.Printf("Received user: %v\n", user)
}
// Bidirecional streaming: WatchUsers
ctx, cancel = context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
stream2, err := client.WatchUsers(ctx)
if err != nil {
log.Fatalf("WatchUsers failed: %v", err)
}
// Enviar alguns usuários
go func() {
for i := 1; i <= 3; i++ {
stream2.Send(&pb.User{
Id: int32(i),
Name: fmt.Sprintf("User %d", i),
Email: fmt.Sprintf("user%d@example.com", i),
Age: 20 + int32(i),
})
time.Sleep(500 * time.Millisecond)
}
stream2.CloseSend()
}()
// Receber eventos
for {
event, err := stream2.Recv()
if err != nil {
fmt.Println("Watch finished")
break
}
fmt.Printf("Event: %v\n", event)
}
}</code></pre>
<p>Em streaming de cliente, chamamos <code>stream.Recv()</code> em um loop para receber mensagens até que o stream termine (erro EOF). Para bidirecional, executamos o envio em uma goroutine paralela enquanto a goroutine principal recebe eventos.</p>
<h2>Interceptors: Middleware Poderoso do gRPC</h2>
<h3>Entendendo Interceptors</h3>
<p>Interceptors são como middleware HTTP — eles interceptam chamadas de RPC antes de chegar ao handler e depois que a resposta é gerada. Use interceptors para logging, autenticação, autorização, rate limiting, métricas e observabilidade. Existem unary interceptors (para chamadas normais) e stream interceptors (para streaming).</p>
<h3>Implementando um Unary Interceptor</h3>
<p>Um exemplo prático: um interceptor que loga todas as chamadas RPC:</p>
<pre><code class="language-go">package main
import (
"context"
"fmt"
"log"
"net"
"time"
pb "github.com/seu-usuario/seu-projeto/pb/user"
"google.golang.org/grpc"
)
type userServer struct {
pb.UnimplementedUserServiceServer
users map[int32]*pb.User
nextID int32
}
func (s userServer) CreateUser(ctx context.Context, req pb.CreateUserRequest) (*pb.CreateUserResponse, error) {
s.nextID++
user := &pb.User{
Id: s.nextID,
Name: req.Name,
Email: req.Email,
Age: req.Age,
}
s.users[user.Id] = user
return &pb.CreateUserResponse{
User: user,
Message: "User created successfully",
}, nil
}
func (s userServer) GetUser(ctx context.Context, req pb.User) (*pb.User, error) {
user, ok := s.users[req.Id]
if !ok {
return nil, fmt.Errorf("user not found")
}
return user, nil
}
// Unary Interceptor para logging
func loggingUnaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
start := time.Now()
fmt.Printf("[%s] RPC iniciado: %s\n", time.Now().Format(time.RFC3339), info.FullMethod)
resp, err := handler(ctx, req)
duration := time.Since(start)
fmt.Printf("[%s] RPC finalizado: %s (duração: %v, erro: %v)\n",
time.Now().Format(time.RFC3339), info.FullMethod, duration, err)
return resp, err
}
func main() {
listener, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("Failed to listen: %v", err)
}
// Criar servidor com interceptor
grpcServer := grpc.NewServer(
grpc.UnaryInterceptor(loggingUnaryInterceptor),
)
pb.RegisterUserServiceServer(grpcServer, &userServer{
users: make(map[int32]*pb.User),
nextID: 0,
})
fmt.Println("Server running on :50051")
if err := grpcServer.Serve(listener); err != nil {
log.Fatalf("Failed to serve: %v", err)
}
}</code></pre>
<p>O interceptor recebe o contexto, a requisição, informações do RPC e o handler (a função real que será executada). Ele pode inspecionar/modificar o contexto e requisição antes de chamar <code>handler()</code>, e processar a resposta/erro depois. Neste exemplo, medimos o tempo de execução.</p>
<h3>Interceptor de Autenticação</h3>
<p>Um exemplo mais prático: validar um token JWT:</p>
<pre><code class="language-go">package main
import (
"context"
"fmt"
"log"
"net"
"strings"
"time"
pb "github.com/seu-usuario/seu-projeto/pb/user"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
type userServer struct {
pb.UnimplementedUserServiceServer
users map[int32]*pb.User
nextID int32
}
func (s userServer) CreateUser(ctx context.Context, req pb.CreateUserRequest) (*pb.CreateUserResponse, error) {
s.nextID++
user := &pb.User{
Id: s.nextID,
Name: req.Name,
Email: req.Email,
Age: req.Age,
}
s.users[user.Id] = user
return &pb.CreateUserResponse{
User: user,
Message: "User created successfully",
}, nil
}
func (s userServer) GetUser(ctx context.Context, req pb.User) (*pb.User, error) {
user, ok := s.users[req.Id]
if !ok {
return nil, fmt.Errorf("user not found")
}
return user, nil
}
// Interceptor de autenticação
func authUnaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, fmt.Errorf("missing metadata")
}
tokens := md.Get("authorization")
if len(tokens) == 0 {
return nil, fmt.Errorf("missing authorization token")
}
token := tokens[0]
if !strings.HasPrefix(token, "Bearer ") {
return nil, fmt.Errorf("invalid token format")
}
token = strings.TrimPrefix(token, "Bearer ")
if token != "valid-secret-token" {
return nil, fmt.Errorf("invalid or expired token")
}
return handler(ctx, req)
}
func main() {
listener, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("Failed to listen: %v", err)
}
grpcServer := grpc.NewServer(
grpc.UnaryInterceptor(authUnaryInterceptor),
)
pb.RegisterUserServiceServer(grpcServer, &userServer{
users: make(map[int32]*pb.User),
nextID: 0,
})
fmt.Println("Server running on :50051")
if err := grpcServer.Serve(listener); err != nil {
log.Fatalf("Failed to serve: %v", err)
}
}</code></pre>
<p>O servidor agora rejeita requisições sem um header <code>authorization</code> válido. O cliente precisa enviar esse header:</p>
<pre><code class="language-go">package main
import (
"context"
"fmt"
"log"
"time"
pb "github.com/seu-usuario/seu-projeto/pb/user"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
func main() {
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
if err != nil {
log.Fatalf("Failed to connect: %v", err)
}
defer conn.Close()
client := pb.NewUserServiceClient(conn)
// Adicionar metadata (header) com token
ctx := context.Background()
ctx = metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer valid-secret-token")
ctx, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
resp, err := client.CreateUser(ctx, &pb.CreateUserRequest{
Name: "João Silva",
Email: "joao@example.com",
Age: 28,
})
if err != nil {
log.Fatalf("CreateUser failed: %v", err)
}
fmt.Printf("Created user: %v\n", resp.User)
}</code></pre>
<h3>Stream Interceptor</h3>
<p>Para streaming, use <code>grpc.StreamInterceptor</code>:</p>
<pre><code class="language-go">package main
import (
"context"
"fmt"
"log"
pb "github.com/seu-usuario/seu-projeto/pb/user"
"google.golang.org/grpc"
)
type userServer struct {
pb.UnimplementedUserServiceServer
users map[int32]*pb.User
nextID int32
}
func (s userServer) ListUsers(req pb.ListUsersRequest, stream grpc.ServerStream) error {
count := int32(0)
for _, user := range s.users {
if count >= req.Limit && req.Limit > 0 {
break
}
if err := stream.SendMsg(user); err != nil {
return err
}
count++
}
return nil
}
// Stream Interceptor para logging
func loggingStreamInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
fmt.Printf("Stream iniciado: %s\n", info.FullMethod)
err := handler(srv, ss)
fmt.Printf("Stream finalizado: %s (erro: %v)\n", info.FullMethod, err)
return err
}
func main() {
listener, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("Failed to listen: %v", err)
}
grpcServer := grpc.NewServer(
grpc.StreamInterceptor(loggingStreamInterceptor),
)
pb.RegisterUserServiceServer(grpcServer, &userServer{
users: make(map[int32]*pb.User),
nextID: 0,
})
fmt.Println("Server running on :50051")
if err := grpcServer.Serve(listener); err != nil {
log.Fatalf("Failed to serve: %v", err)
}
}</code></pre>
<h3>Encadeando Múltiplos Interceptors</h3>
<p>Se precisar de vários interceptors, use bibliotecas como <code>go-grpc-middleware</code> ou encadeie manualmente:</p>
<pre><code class="language-go">func chainUnaryInterceptors(interceptors ...grpc.UnaryServerInterceptor) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
for i := len(interceptors) - 1; i >= 0; i-- {
next := handler
current := interceptors[i]
func(c grpc.UnaryServerInterceptor, n grpc.UnaryHandler) {
handler = func(ctx context.Context, req interface{}) (interface{}, error) {
return c(ctx, req, info, n)
}
}(current, next)
}
return handler(ctx, req)
}
}
// Uso
grpcServer := grpc.NewServer(
grpc.UnaryInterceptor(chainUnaryInterceptors(loggingUnaryInterceptor, authUnaryInterceptor)),
)</code></pre>
<p>Na prática, use <code>github.com/grpc-ecosystem/go-grpc-middleware</code> que oferece várias implementações prontas e testadas.</p>
<h2>Conclusão</h2>
<p>Você aprendeu que <strong>gRPC com Protocol Buffers oferece uma base sólida para sistemas distribuídos modernos</strong>, fornecendo serialização eficiente, tipagem forte e contrato claro entre cliente e servidor. A configuração inicial pode parecer verbosa (definir <code>.proto</code>, gerar código, implementar servidores), mas essa estrutura traz muito mais manutenibilidade do que APIs REST desorganizadas.</p>
<p>O <strong>streaming em gRPC resolve casos de uso que seriam ineficientes em REST</strong>, como monitoramento em tempo real, upload/download de arquivos grandes e comunicação bidirecional. Server streaming e bidirecional são primitivas poderosas que emergem naturalmente da arquitetura HTTP/2 do gRPC.</p>
<p>Por fim, <strong>interceptors são a chave para adicionar funcionalidades transversais sem poluir o código de negócio</strong>, simplificando autenticação, logging, rate limiting e observabilidade. Combine Protocol Buffers bem definidos, streaming apropriado e interceptors bem estruturados, e você terá uma base sólida para microsserviços de produção em Go.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://grpc.io/docs/languages/go/" target="_blank" rel="noopener noreferrer">gRPC Official Documentation - Go</a></li>
<li><a href="https://developers.google.com/protocol-buffers/docs/proto3" target="_blank" rel="noopener noreferrer">Protocol Buffers v3 Language Guide</a></li>
<li><a href="https://github.com/grpc/grpc-go" target="_blank" rel="noopener noreferrer">grpc-go GitHub Repository</a></li>
<li><a href="https://github.com/grpc-ecosystem/go-grpc-middleware" target="_blank" rel="noopener noreferrer">gRPC Ecosystem - Go Middleware</a></li>
<li><a href="https://www.youtube.com/watch?v=FMq5euaAkVw" target="_blank" rel="noopener noreferrer">Building Microservices with gRPC - Alex Xu</a></li>
</ul>
<p><!-- FIM --></p>