gRPC: definição com protobuf e geração de código

1. Introdução ao gRPC e Protocol Buffers no ecossistema Go

gRPC é um framework de chamada de procedimento remoto (RPC) de alto desempenho desenvolvido pelo Google, que utiliza HTTP/2 como transporte e Protocol Buffers como linguagem de definição de interface (IDL). No ecossistema Go, o gRPC se destaca por sua eficiência, tipagem forte e geração automática de código, eliminando a necessidade de escrever manualmente código de serialização e transporte.

O Protocol Buffers (protobuf) permite definir a estrutura dos dados e os contratos dos serviços em arquivos .proto. A partir dessas definições, ferramentas geram código Go com stubs de cliente e servidor, garantindo que ambos os lados sigam exatamente o mesmo contrato.

O fluxo de trabalho típico é: escrever a definição .proto → executar o compilador protoc com plugins Go → implementar a lógica do servidor e consumir os stubs do cliente.

2. Instalação e configuração do ambiente

Antes de começar, instale as ferramentas necessárias:

# Instalar protoc (compilador protobuf) - Linux/macOS
# Baixe de https://github.com/protocolbuffers/protobuf/releases

# Instalar plugins Go
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

# Verificar instalação
protoc --version
protoc-gen-go --version
protoc-gen-go-grpc --version

Em seguida, crie um módulo Go e adicione as dependências:

go mod init github.com/seu-usuario/grpc-exemplo
go get google.golang.org/grpc
go get google.golang.org/protobuf

3. Definindo serviços e mensagens com protobuf

Crie um arquivo proto/hello.proto:

syntax = "proto3";

package hello;

option go_package = "github.com/seu-usuario/grpc-exemplo/proto/hello";

message HelloRequest {
  string name = 1;
  int32 age = 2;
}

message HelloResponse {
  string greeting = 1;
}

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloResponse);
}

A declaração go_package define o caminho do pacote Go gerado. Campos escalares como string e int32 são mapeados automaticamente para tipos Go. Enumerações e campos repetidos (repeated) também são suportados.

4. Tipos avançados de RPC no protobuf

Além do RPC unário, o protobuf suporta três tipos de streaming:

service AdvancedService {
  // Server-side streaming: servidor envia múltiplas respostas
  rpc ServerStream(Request) returns (stream Response);

  // Client-side streaming: cliente envia múltiplos requests
  rpc ClientStream(stream Request) returns (Response);

  // Bidirectional streaming: comunicação full-duplex
  rpc BidirectionalStream(stream Request) returns (stream Response);
}

No Go, cada tipo gera interfaces específicas. Por exemplo, para streaming bidirecional, o servidor implementa um método que recebe um stream e pode chamar stream.Recv() e stream.Send() concorrentemente.

5. Geração de código Go a partir do protobuf

Execute o comando protoc para gerar os stubs:

protoc --go_out=. --go-grpc_out=. proto/hello.proto

Isso gera dois arquivos no diretório proto/hello/:
- hello.pb.go: tipos de mensagens (structs, getters, serialização)
- hello_grpc.pb.go: interfaces de serviço, clientes e servidores

A interface gerada para o servidor será algo como:

type GreeterServer interface {
  SayHello(context.Context, *HelloRequest) (*HelloResponse, error)
  mustEmbedUnimplementedGreeterServer()
}

Organize os arquivos em diretórios separados: proto/ para definições e código gerado, server/ e client/ para implementações.

6. Implementação do servidor gRPC em Go

Crie server/main.go:

package main

import (
  "context"
  "log"
  "net"

  "google.golang.org/grpc"
  pb "github.com/seu-usuario/grpc-exemplo/proto/hello"
)

type server struct {
  pb.UnimplementedGreeterServer
}

func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) {
  greeting := "Olá, " + req.Name + "!"
  if req.Age > 0 {
    greeting += " Você tem " + string(rune(req.Age)) + " anos."
  }
  return &pb.HelloResponse{Greeting: greeting}, nil
}

func main() {
  lis, err := net.Listen("tcp", ":50051")
  if err != nil {
    log.Fatalf("falha ao escutar: %v", err)
  }

  s := grpc.NewServer()
  pb.RegisterGreeterServer(s, &server{})

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

O servidor implementa a interface GreeterServer, registra o serviço no grpc.Server e inicia a escuta. Use context.Context para propagar cancelamentos e deadlines.

7. Implementação do cliente gRPC em Go

Crie client/main.go:

package main

import (
  "context"
  "log"
  "time"

  "google.golang.org/grpc"
  "google.golang.org/grpc/credentials/insecure"
  pb "github.com/seu-usuario/grpc-exemplo/proto/hello"
)

func main() {
  conn, err := grpc.Dial("localhost:50051",
    grpc.WithTransportCredentials(insecure.NewCredentials()),
  )
  if err != nil {
    log.Fatalf("falha ao conectar: %v", err)
  }
  defer conn.Close()

  client := pb.NewGreeterClient(conn)

  ctx, cancel := context.WithTimeout(context.Background(), time.Second)
  defer cancel()

  resp, err := client.SayHello(ctx, &pb.HelloRequest{Name: "Mundo", Age: 42})
  if err != nil {
    log.Fatalf("erro na chamada: %v", err)
  }

  log.Printf("Resposta: %s", resp.Greeting)
}

Para streams, use Recv() e Send():

stream, err := client.ServerStream(ctx, &pb.Request{})
for {
  resp, err := stream.Recv()
  if err == io.EOF {
    break
  }
  if err != nil {
    log.Fatal(err)
  }
  log.Printf("Recebido: %v", resp)
}

8. Boas práticas e próximos passos

  • Versionamento de protobufs: Use ferramentas como buf.build ou git submodules para gerenciar alterações nos contratos sem quebrar compatibilidade.
  • Interceptors: Implemente logging, autenticação e métricas usando interceptors do gRPC, que permitem processar chamadas de forma transversal.
  • Deadlines e timeouts: Sempre propague contextos com deadlines para evitar chamadas órfãs.
  • Testes: Utilize o pacote grpc/testing para criar testes de integração com servidores em memória.

Este artigo é parte de uma série sobre Golang. Nos próximos artigos, exploraremos métricas com Prometheus, documentação com Swagger e streaming bidirecional avançado.

Referências