---
title: "Go e gRPC: Comunicação entre Serviços Completo"
url: "https://golang.com.br/tutoriais/go-grpc-tutorial/"
markdown_url: "https://golang.com.br/tutoriais/go-grpc-tutorial.MD"
description: "Aprenda a criar APIs eficientes com gRPC em Go. Tutorial completo cobrindo Protocol Buffers, streaming, interceptores e testes."
date: "2026-02-11"
author: "Hugo Developer"
---

# Go e gRPC: Comunicação entre Serviços Completo

Aprenda a criar APIs eficientes com gRPC em Go. Tutorial completo cobrindo Protocol Buffers, streaming, interceptores e testes.


# Go e gRPC: Comunicação entre Serviços

O **gRPC** tornou-se o padrão de facto para comunicação entre microserviços em ambientes de alta performance. Desenvolvido pelo Google, ele oferece vantagens significativas sobre REST tradicional, especialmente quando combinado com a eficiência do **Go**.

Neste tutorial completo, você vai aprender a construir serviços gRPC robustos em Go, desde o básico até técnicas avançadas como streaming bidirecional e interceptores.

## Por Que Usar gRPC em Go?

### gRPC vs REST: Comparativo

| Característica | REST/HTTP JSON | gRPC |
|---------------|----------------|------|
| **Formato** | JSON (texto) | Protocol Buffers (binário) |
| **Performance** | ~15x mais lento | ~15x mais rápido |
| **Payload** | Verbos, repetitivo | Compacto, eficiente |
| **Tipagem** | Fraca (runtime) | Forte (compile-time) |
| **Streaming** | Complicado (SSE/WebSocket) | Nativo e simples |
| **Code Generation** | Manual | Automático |
| **Browser Support** | Nativo | Requer gRPC-Web |

**Quando escolher gRPC:**
- Comunicação entre microserviços internos
- APIs de alta performance
- Streaming de dados em tempo real
- Ambientes com largura de banda limitada

**Quando escolher REST:**
- APIs públicas para clientes externos
- Integração com browsers (sem gRPC-Web)
- APIs simples CRUD
- Quando simplicidade é prioridade

## Protocol Buffers: A Base do gRPC

O **Protocol Buffers** (protobuf) é o formato de serialização usado pelo gRPC. Ele define contratos de API de forma clara e gera código automaticamente.

### Instalando as Ferramentas

```bash
# Instalar o compilador protobuf
# macOS
brew install protobuf

# Ubuntu/Debian
apt-get install -y protobuf-compiler

# 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
```

### Definindo Seu Primeiro Serviço

Crie o arquivo `proto/calculator.proto`:

```protobuf
syntax = "proto3";

option go_package = "github.com/seuusuario/calculadora/proto";

package calculator;

// Serviço de calculadora
service Calculator {
  // Operação simples
  rpc Add (OperationRequest) returns (OperationResponse);
  rpc Subtract (OperationRequest) returns (OperationResponse);
  rpc Multiply (OperationRequest) returns (OperationResponse);
  rpc Divide (OperationRequest) returns (OperationResponse);
  
  // Streaming
  rpc StreamOperations (stream OperationRequest) returns (stream OperationResponse);
}

// Mensagem de requisição
message OperationRequest {
  double num1 = 1;
  double num2 = 2;
  string operation_id = 3;
}

// Mensagem de resposta
message OperationResponse {
  double result = 1;
  bool success = 2;
  string error_message = 3;
}
```

### Gerando Código Go

```bash
protoc --go_out=. --go_opt=paths=source_relative \
       --go-grpc_out=. --go-grpc_opt=paths=source_relative \
       proto/calculator.proto
```

Isso gera dois arquivos:
- `proto/calculator.pb.go` - Structs das mensagens
- `proto/calculator_grpc.pb.go` - Interfaces do serviço e cliente

## Criando um Servidor gRPC

### Estrutura do Projeto

```
calculadora-grpc/
├── proto/
│   ├── calculator.proto
│   ├── calculator.pb.go
│   └── calculator_grpc.pb.go
├── server/
│   └── main.go
├── client/
│   └── main.go
└── go.mod
```

### Implementando o Servidor

Crie `server/main.go`:

```go
package main

import (
	"context"
	"fmt"
	"log"
	"net"

	pb "github.com/seuusuario/calculadora/proto"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

// server implementa a interface CalculatorServer
type server struct {
	pb.UnimplementedCalculatorServer
}

// Add implementa a operação de soma
func (s *server) Add(ctx context.Context, req *pb.OperationRequest) (*pb.OperationResponse, error) {
	result := req.Num1 + req.Num2
	
	log.Printf("Operação: %.2f + %.2f = %.2f", req.Num1, req.Num2, result)
	
	return &pb.OperationResponse{
		Result:  result,
		Success: true,
	}, nil
}

// Subtract implementa a operação de subtração
func (s *server) Subtract(ctx context.Context, req *pb.OperationRequest) (*pb.OperationResponse, error) {
	return &pb.OperationResponse{
		Result:  req.Num1 - req.Num2,
		Success: true,
	}, nil
}

// Multiply implementa a operação de multiplicação
func (s *server) Multiply(ctx context.Context, req *pb.OperationRequest) (*pb.OperationResponse, error) {
	return &pb.OperationResponse{
		Result:  req.Num1 * req.Num2,
		Success: true,
	}, nil
}

// Divide implementa a operação de divisão com tratamento de erro
func (s *server) Divide(ctx context.Context, req *pb.OperationRequest) (*pb.OperationResponse, error) {
	if req.Num2 == 0 {
		// Retornar erro gRPC apropriado
		return nil, status.Error(codes.InvalidArgument, "divisão por zero não permitida")
	}
	
	return &pb.OperationResponse{
		Result:  req.Num1 / req.Num2,
		Success: true,
	}, nil
}

func main() {
	// Criar listener TCP
	listener, err := net.Listen("tcp", ":50051")
	if err != nil {
		log.Fatalf("Falha ao criar listener: %v", err)
	}

	// Criar servidor gRPC
	grpcServer := grpc.NewServer()
	
	// Registrar serviço
	pb.RegisterCalculatorServer(grpcServer, &server{})

	fmt.Println("Servidor gRPC iniciado na porta :50051")
	
	// Iniciar servidor
	if err := grpcServer.Serve(listener); err != nil {
		log.Fatalf("Falha ao iniciar servidor: %v", err)
	}
}
```

### Inicializando e Executando

```bash
# Inicializar módulo
go mod init github.com/seuusuario/calculadora

# Baixar dependências
go mod tidy

# Executar servidor
go run server/main.go
# Saída: Servidor gRPC iniciado na porta :50051
```

## Criando um Cliente gRPC

### Cliente Básico

Crie `client/main.go`:

```go
package main

import (
	"context"
	"fmt"
	"log"
	"time"

	pb "github.com/seuusuario/calculadora/proto"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

func main() {
	// Configurar conexão com timeout
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// Conectar ao servidor
	conn, err := grpc.DialContext(ctx, "localhost:50051",
		grpc.WithTransportCredentials(insecure.NewCredentials()),
		grpc.WithBlock(),
	)
	if err != nil {
		log.Fatalf("Falha ao conectar: %v", err)
	}
	defer conn.Close()

	// Criar cliente
	client := pb.NewCalculatorClient(conn)

	// Criar contexto para requisições
	requestCtx, requestCancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer requestCancel()

	// Testar operações
	operations := []struct {
		name string
		num1 float64
		num2 float64
		op   func(context.Context, *pb.OperationRequest) (*pb.OperationResponse, error)
	}{
		{"Soma", 10, 5, client.Add},
		{"Subtração", 10, 5, client.Subtract},
		{"Multiplicação", 10, 5, client.Multiply},
		{"Divisão", 10, 5, client.Divide},
	}

	for _, op := range operations {
		req := &pb.OperationRequest{
			Num1:         op.num1,
			Num2:         op.num2,
			OperationId:  fmt.Sprintf("op-%s", op.name),
		}

		resp, err := op.op(requestCtx, req)
		if err != nil {
			log.Printf("Erro em %s: %v", op.name, err)
			continue
		}

		fmt.Printf("%s: %.2f e %.2f = %.2f (sucesso: %v)\n",
			op.name, op.num1, op.num2, resp.Result, resp.Success)
	}

	// Testar divisão por zero
	_, err = client.Divide(requestCtx, &pb.OperationRequest{
		Num1: 10,
		Num2: 0,
	})
	if err != nil {
		fmt.Printf("Erro esperado na divisão por zero: %v\n", err)
	}
}
```

### Executando o Cliente

```bash
# Em outro terminal
go run client/main.go
```

## Streaming em gRPC

Um dos diferenciais do gRPC é o suporte nativo a streaming. Vamos implementar streaming bidirecional:

### Adicionando Streaming ao Proto

```protobuf
service Calculator {
  // ... métodos anteriores ...
  
  // Streaming de estatísticas
  rpc StreamStats (stream Number) returns (stream StatsResponse);
}

message Number {
  double value = 1;
}

message StatsResponse {
  double count = 1;
  double sum = 2;
  double average = 3;
  double min = 4;
  double max = 5;
}
```

### Implementando Streaming no Servidor

```go
func (s *server) StreamStats(stream pb.Calculator_StreamStatsServer) error {
	var count int64
	var sum, min, max float64
	first := true

	for {
		// Receber número do cliente
		num, err := stream.Recv()
		if err == io.EOF {
			return nil
		}
		if err != nil {
			return err
		}

		// Atualizar estatísticas
		count++
		sum += num.Value

		if first {
			min = num.Value
			max = num.Value
			first = false
		} else {
			if num.Value < min {
				min = num.Value
			}
			if num.Value > max {
				max = num.Value
			}
		}

		average := sum / float64(count)

		// Enviar estatísticas atualizadas
		resp := &pb.StatsResponse{
			Count:   float64(count),
			Sum:     sum,
			Average: average,
			Min:     min,
			Max:     max,
		}

		if err := stream.Send(resp); err != nil {
			return err
		}
	}
}
```
### Cliente com Streaming

```go
func testStreaming(client pb.CalculatorClient) {
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	stream, err := client.StreamStats(ctx)
	if err != nil {
		log.Fatalf("Erro ao iniciar stream: %v", err)
	}

	// Goroutine para receber respostas
	go func() {
		for {
			resp, err := stream.Recv()
			if err == io.EOF {
				return
			}
			if err != nil {
				log.Printf("Erro ao receber: %v", err)
				return
			}
			
			fmt.Printf("Stats: count=%.0f, sum=%.2f, avg=%.2f, min=%.2f, max=%.2f\n",
				resp.Count, resp.Sum, resp.Average, resp.Min, resp.Max)
		}
	}()

	// Enviar números
	numbers := []float64{10, 20, 30, 40, 50}
	for _, n := range numbers {
		if err := stream.Send(&pb.Number{Value: n}); err != nil {
			log.Printf("Erro ao enviar: %v", err)
			return
		}
		time.Sleep(500 * time.Millisecond)
	}

	stream.CloseSend()
	time.Sleep(time.Second)
}
```

## Interceptores (Middleware)

Interceptores permitem adicionar comportamentos cross-cutting como logging, autenticação e métricas.

### Interceptor de Logging

```go
func loggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
	start := time.Now()
	
	log.Printf("[gRPC] Método: %s | Requisição: %+v", info.FullMethod, req)
	
	resp, err := handler(ctx, req)
	
	duration := time.Since(start)
	
	if err != nil {
		log.Printf("[gRPC] Método: %s | Erro: %v | Duração: %v", info.FullMethod, err, duration)
	} else {
		log.Printf("[gRPC] Método: %s | Resposta: %+v | Duração: %v", info.FullMethod, resp, duration)
	}
	
	return resp, err
}

// Registrar no servidor
grpcServer := grpc.NewServer(
	grpc.UnaryInterceptor(loggingInterceptor),
)
```

### Interceptor de Autenticação

```go
func authInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
	// Extrair metadados
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		return nil, status.Error(codes.Unauthenticated, "metadados não encontrados")
	}

	// Verificar token
	tokens := md.Get("authorization")
	if len(tokens) == 0 {
		return nil, status.Error(codes.Unauthenticated, "token não fornecido")
	}

	token := strings.TrimPrefix(tokens[0], "Bearer ")
	if !isValidToken(token) {
		return nil, status.Error(codes.Unauthenticated, "token inválido")
	}

	// Adicionar claims ao contexto
	ctx = context.WithValue(ctx, "user_id", getUserIDFromToken(token))
	
	return handler(ctx, req)
}

func isValidToken(token string) bool {
	// Implementar validação JWT
	return token == "seu-token-secreto"
}
```

### Cliente com Autenticação

```go
func authInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
	// Adicionar token às requisições
	ctx = metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer seu-token-aqui")
	return invoker(ctx, method, req, reply, cc, opts...)
}

// Usar no cliente
conn, err := grpc.Dial("localhost:50051",
	grpc.WithTransportCredentials(insecure.NewCredentials()),
	grpc.WithUnaryInterceptor(authInterceptor),
)
```

## Testando Serviços gRPC

### Testes Unitários

```go
package main

import (
	"context"
	"testing"

	pb "github.com/seuusuario/calculadora/proto"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

type mockServer struct {
	pb.UnimplementedCalculatorServer
}

func TestAdd(t *testing.T) {
	s := &server{}
	ctx := context.Background()

	tests := []struct {
		name     string
		req      *pb.OperationRequest
		expected float64
		wantErr  bool
	}{
		{
			name:     "soma positiva",
			req:      &pb.OperationRequest{Num1: 10, Num2: 5},
			expected: 15,
			wantErr:  false,
		},
		{
			name:     "soma com negativos",
			req:      &pb.OperationRequest{Num1: -10, Num2: 5},
			expected: -5,
			wantErr:  false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			resp, err := s.Add(ctx, tt.req)
			
			if (err != nil) != tt.wantErr {
				t.Errorf("Add() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			
			if !tt.wantErr && resp.Result != tt.expected {
				t.Errorf("Add() = %v, want %v", resp.Result, tt.expected)
			}
		})
	}
}

func TestDivideByZero(t *testing.T) {
	s := &server{}
	ctx := context.Background()

	req := &pb.OperationRequest{Num1: 10, Num2: 0}
	_, err := s.Divide(ctx, req)

	if err == nil {
		t.Fatal("esperava erro na divisão por zero")
	}

	st, ok := status.FromError(err)
	if !ok {
		t.Fatal("esperava status gRPC")
	}

	if st.Code() != codes.InvalidArgument {
		t.Errorf("código esperado %v, obtido %v", codes.InvalidArgument, st.Code())
	}
}
```

### Testes de Integração

```go
func TestIntegration(t *testing.T) {
	// Iniciar servidor em porta aleatória
	listener, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatalf("falha ao criar listener: %v", err)
	}

	grpcServer := grpc.NewServer()
	pb.RegisterCalculatorServer(grpcServer, &server{})

	go grpcServer.Serve(listener)
	defer grpcServer.Stop()

	// Conectar cliente
	conn, err := grpc.Dial(listener.Addr().String(),
		grpc.WithTransportCredentials(insecure.NewCredentials()),
	)
	if err != nil {
		t.Fatalf("falha ao conectar: %v", err)
	}
	defer conn.Close()

	client := pb.NewCalculatorClient(conn)
	ctx := context.Background()

	// Testar operações
	resp, err := client.Add(ctx, &pb.OperationRequest{Num1: 10, Num2: 20})
	if err != nil {
		t.Fatalf("Add falhou: %v", err)
	}

	if resp.Result != 30 {
		t.Errorf("resultado incorreto: got %v, want 30", resp.Result)
	}
}
```

## Melhores Práticas

### 1. Tratamento de Erros

Use códigos de status gRPC apropriados:

```go
import "google.golang.org/grpc/codes"
import "google.golang.org/grpc/status"

// Códigos comuns:
// codes.OK           - Sucesso
// codes.NotFound     - Recurso não encontrado
// codes.InvalidArgument - Argumento inválido
// codes.Internal     - Erro interno do servidor
// codes.Unavailable  - Serviço indisponível
// codes.DeadlineExceeded - Timeout

func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
    user, err := s.db.GetUser(req.Id)
    if err == sql.ErrNoRows {
        return nil, status.Errorf(codes.NotFound, "usuário %d não encontrado", req.Id)
    }
    if err != nil {
        return nil, status.Errorf(codes.Internal, "erro ao buscar usuário: %v", err)
    }
    return user, nil
}
```

### 2. Timeouts e Contextos

```go
// Sempre use contextos com timeout
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()

resp, err := client.Operation(ctx, req)
if err != nil {
    if status.Code(err) == codes.DeadlineExceeded {
        // Tratar timeout especificamente
        log.Println("operação excedeu o tempo limite")
    }
}
```

### 3. Health Checks

```go
import "google.golang.org/grpc/health"
import "google.golang.org/grpc/health/grpc_health_v1"

// Registrar health check
healthServer := health.NewServer()
grpc_health_v1.RegisterHealthServer(grpcServer, healthServer)
healthServer.SetServingStatus("", grpc_health_v1.HealthCheckResponse_SERVING)
```

### 4. Load Balancing

```go
// Client-side load balancing
conn, err := grpc.Dial(
	"dns:///servico.exemplo.com",
	grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`),
	grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, "")),
)
```

## Deploy e Monitoramento

### Docker

```dockerfile
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o server ./server

FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/server .
EXPOSE 50051
CMD ["./server"]
```

### Kubernetes

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: calculadora-grpc
spec:
  replicas: 3
  selector:
    matchLabels:
      app: calculadora
  template:
    metadata:
      labels:
        app: calculadora
    spec:
      containers:
      - name: server
        image: calculadora-grpc:latest
        ports:
        - containerPort: 50051
        livenessProbe:
          grpc:
            port: 50051
          initialDelaySeconds: 10
        readinessProbe:
          grpc:
            port: 50051
          initialDelaySeconds: 5
---
apiVersion: v1
kind: Service
metadata:
  name: calculadora-service
spec:
  selector:
    app: calculadora
  ports:
  - port: 50051
    targetPort: 50051
  type: ClusterIP
```

## Próximos Passos

Agora que você domina o básico do gRPC com Go:

1. **Explore service meshes** como Istio ou Linkerd para gerenciamento avançado
2. **Implemente tracing distribuído** com OpenTelemetry
3. **Adicione métricas** com Prometheus
4. **Estude gRPC-Gateway** para expor APIs REST junto com gRPC
5. **Aprenda sobre Protocol Buffers avançado** como oneof, maps e custom options

## Conclusão

O gRPC oferece uma alternativa robusta e eficiente ao REST para comunicação entre serviços. Combinado com Go, você tem uma stack de alta performance para microserviços.

**Pontos chave para lembrar:**
- Protobuf garante contratos de API type-safe
- Streaming é nativo e eficiente
- Interceptores permitem comportamentos cross-cutting
- Testes são simples com as ferramentas certas
- Códigos de status gRPC padronizam tratamento de erros

## FAQ

**Q: Posso usar gRPC com frontend web?**
R: Sim, através do gRPC-Web, que requer um proxy como Envoy entre o browser e o servidor gRPC.

**Q: Como faço para versionar APIs gRPC?**
R: Use package names com versão (ex: `v1`, `v2`) ou mantenha compatibilidade backward com campos opcionais.

**Q: gRPC é compatível com GraphQL?**
R: São tecnologias diferentes, mas você pode usar gRPC-Gateway para gerar REST e então GraphQL via ferramentas como gqlgen.

**Q: Qual a diferença entre unary, server streaming, client streaming e bidirectional streaming?**
R: Unary = 1 req, 1 resp. Server streaming = 1 req, N resp. Client streaming = N req, 1 resp. Bidirectional = N req, N resp simultaneamente.

---

*Continue sua jornada em Go explorando nosso tutorial sobre [Go e Kubernetes](/tutoriais/go-kubernetes/) para deploy de microserviços.*
