O gRPC é um framework de chamada de procedimento remoto (RPC) de alto desempenho desenvolvido pelo Google. Ele permite que programas em diferentes linguagens se comuniquem de forma eficiente, usando HTTP/2 como transporte e Protocol Buffers (Protobuf) como linguagem de definição de interface (IDL) e formato de serialização. Nesta aula, vamos explorar os fundamentos do gRPC no contexto da linguagem Go, desde os conceitos básicos até a implementação prática de um servidor e um cliente.

Diferente de APIs REST tradicionais baseadas em JSON e HTTP/1.1, o gRPC oferece vantagens como multiplexação de streams, comunicação bidirecional e serialização binária compacta, tornando-o ideal para microsserviços e sistemas distribuídos. Com o suporte nativo do Go, você pode criar serviços robustos e escaláveis com relativa facilidade.

O que é gRPC

gRPC é um framework de RPC de código aberto que usa HTTP/2 para transporte e Protocol Buffers para serialização de dados. Ele foi projetado para ser rápido, eficiente e independente de linguagem, permitindo que serviços escritos em Go, Java, Python, etc., se comuniquem de forma transparente. Com gRPC, você define um serviço com métodos e tipos de mensagem em um arquivo .proto, e o gRPC gera código cliente e servidor automaticamente.

O gRPC suporta quatro tipos de comunicação: unária (request-response), streaming de servidor, streaming de cliente e streaming bidirecional. Isso dá flexibilidade para diferentes padrões de comunicação, como envio de eventos ou troca contínua de dados. Além disso, o uso de HTTP/2 permite multiplexação de múltiplas chamadas em uma única conexão, reduzindo latência e melhorando o desempenho.

Exemplo de definição de um serviço simples em um arquivo .proto:

syntax = "proto3";

package exemplo;

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

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

Esse arquivo define um serviço com um método unário que recebe um HelloRequest e retorna um HelloReply. A partir dele, geramos código Go para servidor e cliente.

Protobuf (visão geral)

Protocol Buffers (Protobuf) é um mecanismo de serialização de dados estruturados, desenvolvido pelo Google, que é compacto e eficiente. Ele é usado como IDL no gRPC para definir a estrutura das mensagens e os contratos dos serviços. Protobuf usa um formato binário, o que o torna menor e mais rápido de serializar/deserializar em comparação com JSON ou XML.

Para usar Protobuf em Go, é necessário instalar o compilador protoc e o plugin protoc-gen-go, que gera código Go a partir dos arquivos .proto. O código gerado inclui tipos de dados para as mensagens e interfaces para os serviços. Essa geração automática garante que cliente e servidor estejam sempre em sincronia com a definição.

Exemplo de instalação das ferramentas:

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

Depois, para gerar o código Go, execute:

protoc --go_out=. --go-grpc_out=. exemplo.proto

Isso gerará dois arquivos: exemplo.pb.go (mensagens) e exemplo_grpc.pb.go (interfaces de serviço).

Cliente e servidor

No gRPC, o servidor implementa a interface definida no arquivo .proto e expõe um endpoint HTTP/2. O cliente cria uma conexão com esse endpoint e chama os métodos como se fossem locais. Em Go, usamos o pacote google.golang.org/grpc para criar tanto o servidor quanto o cliente.

Servidor: Criamos um tipo que implementa a interface gerada e registramos no servidor gRPC. O servidor escuta em uma porta TCP e usa o pacote grpc.NewServer().

Exemplo de implementação de servidor para o serviço Greeter:

package main

import (
    "context"
    "log"
    "net"

    "google.golang.org/grpc"
    pb "caminho/para/proto"
)

type server struct {
    pb.UnimplementedGreeterServer
}

func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) {
    return &pb.HelloReply{Message: "Olá, " + req.Name}, 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)
    }
}

Cliente: Criamos uma conexão gRPC com o endereço do servidor e instanciamos o cliente a partir da stub gerada. Depois, chamamos os métodos da mesma forma que uma função local.

Exemplo de cliente:

package main

import (
    "context"
    "log"
    "time"

    "google.golang.org/grpc"
    pb "caminho/para/proto"
)

func main() {
    conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
    if err != nil {
        log.Fatalf("não foi possível conectar: %v", err)
    }
    defer conn.Close()

    c := pb.NewGreeterClient(conn)
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()

    resp, err := c.SayHello(ctx, &pb.HelloRequest{Name: "Mundo"})
    if err != nil {
        log.Fatalf("erro ao chamar SayHello: %v", err)
    }
    log.Printf("Resposta: %s", resp.Message)
}

Note que usamos grpc.WithInsecure() para conexões sem TLS, mas em produção é recomendado usar credenciais seguras.

Quando usar

O gRPC é particularmente útil em ambientes de microsserviços, onde a comunicação entre serviços precisa ser rápida e confiável. Ele também é excelente para cenários que exigem streaming de dados, como feeds de eventos, telemetria ou bate-papo em tempo real. Além disso, por ser baseado em HTTP/2, oferece melhor desempenho e menor latência em comparação com REST/JSON.

No entanto, gRPC não é ideal para todos os casos. Se você precisa de comunicação com navegadores web, onde o suporte a HTTP/2 e Protobuf não é nativo, ou se exige simplicidade e compatibilidade ampla, REST pode ser mais adequado. Além disso, o gRPC pode ter uma curva de aprendizado maior e a depuração de payloads binários é mais difícil.

Em resumo, use gRPC quando você tem controle sobre ambos os lados da comunicação (cliente e servidor), precisa de alta performance, streaming ou quando está construindo uma arquitetura de microsserviços. Avalie os trade-offs antes de decidir.

Boas práticas

  • Sempre defina versões de API no seu arquivo .proto, por exemplo, usando pacotes com sufixo v1, v2, etc.
  • Utilize contextos com timeouts para evitar chamadas bloqueantes.
  • Em produção, configure credenciais TLS para comunicação segura.
  • Organize seus arquivos .proto em um diretório separado e compartilhe entre clientes e servidores.
  • Considere usar interceptors para logging, autenticação ou métricas.

Referências

Exercícios

  1. Explique a diferença entre gRPC e REST em termos de transporte e serialização.

    ✓ Resposta: O gRPC usa HTTP/2 como transporte e serializa dados em formato binário (Protobuf), enquanto REST normalmente usa HTTP/1.1 e JSON (texto). Isso torna o gRPC mais rápido e eficiente, além de suportar streaming bidirecional, mas REST é mais simples e compatível com navegadores.
  2. Escreva um arquivo .proto que defina um serviço chamado 'Calculator' com um método 'Add' que recebe dois números inteiros e retorna a soma.

    ✓ Resposta:
    syntax = "proto3";
    
    package calc;
    
    service Calculator {
      rpc Add (AddRequest) returns (AddReply);
    }
    
    message AddRequest {
      int32 a = 1;
      int32 b = 2;
    }
    
    message AddReply {
      int32 result = 1;
    }
  3. Qual a finalidade do comando 'protoc --go_out=. --go-grpc_out=. exemplo.proto'?

    ✓ Resposta: Esse comando gera o código Go a partir do arquivo exemplo.proto. O primeiro --go_out gera o código das mensagens (estruturas de dados) e o --go-grpc_out gera o código das interfaces de serviço (stubs para cliente e servidor).
  4. Cite duas situações em que gRPC é mais adequado que REST.

    ✓ Resposta: 1) Comunicação entre microsserviços que exige alta performance e baixa latência. 2) Cenários que precisam de streaming de dados, como envio de logs em tempo real ou feeds de eventos.
  5. No exemplo de servidor, qual é o papel do método 'RegisterGreeterServer'?

    ✓ Resposta: Esse método registra a implementação do serviço no servidor gRPC, associando o tipo 'server' à interface GreeterServer. Isso permite que o servidor saiba como lidar com as chamadas recebidas para o serviço Greeter.