Knowledge

gRPC vs REST vs GraphQL: Escolhendo o protocolo certo para microsserviços

REST, GraphQL e gRPC resolvem problemas diferentes. Entender seus trade-offs em latência, governança de schema e suporte de ecossistema evita arrependimento arquitetural.

13/03/20268 min de leituraKnowledge
gRPC vs REST vs GraphQL: Escolhendo o protocolo certo para microsserviços

Resumo executivo

REST, GraphQL e gRPC resolvem problemas diferentes. Entender seus trade-offs em latência, governança de schema e suporte de ecossistema evita arrependimento arquitetural.

Ultima atualizacao: 13/03/2026

Introdução: A escolha de protocolo é arquitetural, não cosmética

Em 2026, o debate entre REST, GraphQL e gRPC amadureceu de "qual é melhor" para "qual se adapta a este problema específico". Cada protocolo endereça diferentes restrições: eficiência de rede, governança de schema, flexibilidade de cliente e complexidade operacional.

Equipes maduras executam os três em produção, escolhendo a ferramenta baseado no contexto de comunicação:

  • gRPC para comunicação síncrona de alto throughput entre serviços
  • REST para APIs públicas e integrações externas
  • GraphQL para aplicações de cliente intensivas em dados com necessidades variáveis de fetch

O custo de escolher incorretamente se compõe: incompatibilidades de performance, overhead de governança de schema, ou complexidade operacional que as equipes lutam para manter.

Matriz de comparação de protocolos

PreocupaçãoREST (HTTP/JSON)GraphQLgRPC (HTTP/2)
LatênciaAlta (HTTP/1.1, parse JSON)Moderada (único round-trip, JSON)Baixa (HTTP/2, binário, protobuf)
BandaAlta (JSON verboso, headers)Flexível (payload específico da query)Baixa (protobuf compacto)
Type SafetyRuntime (OpenAPI ajuda mas não é forçado)Schema (sistema de tipos na query)Compile-time (schemas protobuf)
Evolução de SchemaVersiona URLs ou negociação de conteúdoAditivo apenas (sem breaking changes)Regras de evolução protobuf
Flexibilidade de ClienteBaixa (endpoints definidos pelo servidor)Alta (cliente define queries)Baixa (métodos definidos pelo servidor)
StreamingLimitado (Server-Sent Events)Subscription (WebSockets)Streaming bidirecional (nativo)
Suporte de BrowserUniversalRequer biblioteca de clienteRequer transpilação/ferramentas
ToolingEcossistema maduroEcossistema crescendoMaduro mas especializado
ObservabilidadeExcelente (métricas HTTP padrão)Bom (analytics de queries)Bom (requer instrumentação)

Quando gRPC vence: Mesh de serviços internos

Características de performance

gRPC usa Protocol Buffers para serialização, oferecendo payloads significativamente menores comparado ao JSON:

protobuf// user_service.proto
syntax = "proto3";

package user;

service UserService {
  rpc GetUser(GetUserRequest) returns (GetUserResponse);
  rpc ListUsers(ListUsersRequest) returns (stream UserResponse);
  rpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse);
}

message GetUserRequest {
  string user_id = 1;
}

message GetUserResponse {
  string user_id = 1;
  string email = 2;
  string name = 3;
  map<string, string> metadata = 4;
}

Código gerado inclui:

  • Stubs de cliente tipados fortemente em 10+ linguagens
  • Serialização/desserialização embutida
  • Validação de schema em tempo de compilação

Capacidades de streaming

O streaming nativo do gRPC é poderoso para casos de uso em tempo real:

go// server/main.go (implementação Go)
func (s *userService) ListUsers(req *user.ListUsersRequest, stream user.UserService_ListUsersServer) error {
    // Stream usuários conforme se tornam disponíveis
    for _, user := range s.users {
        if err := stream.Send(&user.UserResponse{
            UserId: user.ID,
            Email:  user.Email,
            Name:   user.Name,
        }); err != nil {
            return err
        }
    }
    return nil
}

Server streaming: Envia múltiplas respostas para único request (ex: stream de logs)

Client streaming: Envia múltiplos requests, recebe única resposta (ex: upload em lote)

Bidirectional streaming: Ambas direções simultaneamente (ex: chat, edição colaborativa)

Trade-offs operacionais

Vantagens:

  • Latência 10-100x menor para serviços de alto throughput
  • Payload compacto reduz custos de banda
  • Type safety captura erros em tempo de compilação
  • Load balancing embutido (multiplexação HTTP/2)

Custos:

  • Requer tooling Protocol Buffer no pipeline de build
  • Debugging requer ferramentas de decoder protobuf
  • Suporte de browser limitado (requer grpc-web ou similar)
  • Menos familiar para desenvolvedores externos

Casos de uso ideais para gRPC

1. Microsserviços de alto throughput:

  • Sistemas de processamento de pagamentos
  • Pipelines de analytics em tempo real
  • Streams de replicação de banco de dados

2. Requisitos estritos de tipo:

  • Serviços de transação financeira
  • Processamento de dados de saúde
  • Integrações de sistemas legados

3. Workloads de streaming:

  • Notificações em tempo real
  • Features de colaboração ao vivo
  • Ingestão de telemetria IoT

Quando REST vence: APIs públicas e simplicidade

Pontos fortes em 2026

REST continua dominante para APIs públicas por razões práticas:

1. Compatibilidade universal:

typescript// client.js - Funciona em qualquer browser
const response = await fetch('https://api.example.com/users/123', {
  headers: {
    'Authorization': `Bearer ${token}`,
    'Accept': 'application/json',
  },
});
const user = await response.json();

2. Semântica de recursos explícita:

httpGET /users/123              # Recupera usuário
PUT /users/123              # Atualiza usuário
DELETE /users/123            # Deleta usuário
GET /users/123/orders        # Relacionamento aninhado

Verbos HTTP (GET, POST, PUT, DELETE) mapeam limpa- mente para operações CRUD, tornando APIs previsíveis.

3. Ecossistema maduro:

  • OpenAPI/Swagger para documentação
  • Postman, Insomnia para testes
  • Headers padrão de cache HTTP
  • Padrões universais de autenticação (JWT, OAuth)

Padrões de design para REST moderno

Paginação:

httpGET /users?page=2&per_page=50

{
  "data": [...],
  "pagination": {
    "page": 2,
    "per_page": 50,
    "total": 1000,
    "total_pages": 20
  }
}

Tratamento de erros:

httpHTTP/1.1 422 Unprocessable Entity

{
  "errors": [
    {
      "code": "VALIDATION_ERROR",
      "field": "email",
      "message": "Formato de email inválido"
    }
  ]
}

Versionamento:

/api/v1/users          # Versão atual
/api/v2/users          # Features beta

Considerações operacionais

Vantagens:

  • Curva de aprendizado zero para desenvolvedores
  • Ecossistema de ferramentas excelente
  • Nativo de browser (sem polyfills)
  • Debugging simples (curl, DevTools do browser)

Custos:

  • Overfetching/underfetching de dados
  • Múltiplos round-trips para recursos relacionados
  • Nenhuma type safety em tempo de compilação
  • Schema drift entre documentação e implementação

Casos de uso ideais para REST

1. APIs públicas:

  • Integrações de terceiros
  • Apps mobile de múltiplas plataformas
  • Endpoints de webhook

2. Aplicações CRUD simples:

  • Interfaces administrativas
  • Dashboards internos
  • Sistemas de gerenciamento de conteúdo

3. Integração legada:

  • Sistemas antigos esperando HTTP/1.1
  • Sistemas sem tooling protobuf
  • Integrações de parceiros requerendo simplicidade

Quando GraphQL vence: Clientes intensivos em dados

Vantagem de flexibilidade

O valor primário do GraphQL é permitir que clientes especifiquem exatamente quais dados precisam:

graphql# Cliente define a query
query GetUserWithOrders($userId: ID!) {
  user(id: $userId) {
    id
    email
    name
    orders(first: 5) {
      id
      total
      status
      items {
        product {
          name
          price
        }
      }
    }
  }
}

Resultado: Único request retorna usuário, seus pedidos recentes e detalhes de produto—sem overfetching ou underfetching.

Desenvolvimento schema-first

graphql# schema.graphql
type User {
  id: ID!
  email: String!
  name: String!
  orders(first: Int): [Order!]!
}

type Order {
  id: ID!
  total: Float!
  status: OrderStatus!
  items: [OrderItem!]!
}

type Product {
  id: ID!
  name: String!
  price: Float!
}

enum OrderStatus {
  PENDING
  PROCESSING
  COMPLETED
  CANCELLED
}

type Query {
  user(id: ID!): User
}

type Mutation {
  createOrder(input: CreateOrderInput!): Order!
}

Benefícios de governança:

  • Única fonte de verdade para todos os contratos de dados
  • Type safety forçada na query
  • Evolução de schema aditiva (sem breaking changes)

Federação e subgrafos

Para organizações grandes, GraphQL Federation permite desenvolvimento independente de equipe:

graphql# Subgrafo Users
type User @key(fields: "id") {
  id: ID!
  email: String!
  name: String!
}

# Subgrafo Orders
extend type User @key(fields: "id") {
  id: ID! @external
  orders(first: Int): [Order!]!
}

Cada equipe possui seu subgrafo independentemente enquanto o gateway apresenta schema unificado.

Trade-offs operacionais

Vantagens:

  • Flexibilidade de cliente reduz overfetching
  • Único request para dados relacionados
  • Queries fortemente tipadas
  • Sem versionamento de API necessário

Custos:

  • Requer análise de complexidade de queries
  • Problemas de query N+1 se não mitigados
  • Caching mais complexo que REST
  • Requer tooling com consciência de GraphQL

Estratégias de mitigação

1. Queries persistidas:

typescript// Cliente envia ID de query em vez de query completa
POST /graphql
{
  "extensions": {
    "persistedQuery": {
      "version": 1,
      "sha256Hash": "abc123..."
    }
  },
  "operationName": "GetUserWithOrders"
}

2. Limite de profundidade de query:

typescript// Forçar profundidade máxima de query
const MAX_DEPTH = 5;

function validateQueryDepth(ast: ASTNode, depth = 0): boolean {
  if (depth > MAX_DEPTH) return false;
  return ast.selections?.every(s => validateQueryDepth(s, depth + 1));
}

3. Padrão DataLoader:

typescript// Batch requests relacionados para evitar N+1
const userLoader = new DataLoader(async (userIds) => {
  return db.users.find({ where: { id: { in: userIds } } });
});

// No resolver
user: async (parent) => userLoader.load(parent.userId)

Casos de uso ideais para GraphQL

1. Aplicações ricas em dados:

  • Dashboards com necessidades de dados variáveis
  • Apps mobile com restrições de banda
  • Aplicações single-page com UI complexa

2. Organizações multi-time:

  • Squads independentes possuindo diferentes domínios de dados
  • Federação através de múltiplos serviços
  • Iteração rápida de produto

3. Plataformas de desenvolvedores:

  • APIs consumidas por times internos
  • Aplicações com requisitos de dados dinâmicos
  • Ambientes de prototipagem rápida

Arquiteturas híbridas: Usando os três

Padrão de gateway

A maioria das organizações maduras implementa gateways de API que roteiam baseado no caso de uso:

┌─────────────────────────────────────────────────────────────┐
│                    API Gateway                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌────────────┐  ┌────────────┐  ┌────────────┐  │
│  │   REST     │  │  GraphQL   │  │   gRPC     │  │
│  │   Route    │  │   Route    │  │   Route    │  │
│  └─────┬──────┘  └─────┬──────┘  └─────┬──────┘  │
│        │                │                │                │
│        ▼                ▼                ▼                │
│  ┌────────────┐  ┌────────────┐  ┌────────────┐  │
│  │  Public    │  │  Web App   │  │  Internal  │  │
│  │   API      │  │   Client   │  │  Services  │  │
│  └────────────┘  └────────────┘  └────────────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Exemplo: Plataforma e-commerce

API Pública (REST):

GET /public/products/123
POST /public/checkout

Cliente web (GraphQL):

graphqlquery ProductPage($productId: ID!) {
  product(id: $productId) {
    id
    name
    price
    reviews(first: 10) {
      rating
      comment
      user { name }
    }
    recommendations { id name price }
  }
}

Serviços internos (gRPC):

protobufservice InventoryService {
  rpc CheckStock(CheckStockRequest) returns (CheckStockResponse);
  rpc ReserveStock(ReserveStockRequest) returns (ReserveStockResponse);
}

service PaymentService {
  rpc ProcessPayment(PaymentRequest) returns (PaymentResponse);
  rpc RefundPayment(RefundRequest) returns (RefundResponse);
}

Framework de decisão

Passo 1: Identificar restrições primárias

RestriçãoPreferir gRPCPreferir RESTPreferir GraphQL
Latência é crítica
Suporte universal de browser necessário
Cliente precisa de dados variáveis
API pública
Serviço interno de alto throughput
Requisitos complexos de dados aninhados

Passo 2: Avaliar capacidade da equipe

A equipe pode manter schemas Protocol Buffer?

  • Sim → gRPC é viável
  • Não → REST ou GraphQL

A equipe precisa de expertise em tooling GraphQL?

  • Tem experiência GraphQL → GraphQL é viável
  • Não → REST com OpenAPI

Existe viés de infraestrutura existente?

  • Ecossistema nativo de Kubernetes → gRPC se encaixa bem
  • Deploy pesado de CDN → REST ou GraphQL

Passo 3: Considerar evolução futura

A API tende a mudar frequentemente?

  • Sim → Evolução aditiva de GraphQL ajuda
  • Não → Versionamento REST é suficiente

Múltiplas equipes independentes consumirão esta API?

  • Sim → Federação GraphQL ou REST com OpenAPI
  • Não → Protocolo mais simples pode ser suficiente

Estratégias de migração

De REST para gRPC

Fase 1: Protocolo duplo

go// server/main.go
func main() {
    // Serve REST para compatibilidade retroativa
    go func() {
        log.Fatal(http.ListenAndServe(":8080", restHandler))
    }()

    // Serve gRPC para serviços internos
    listener, _ := net.Listen("tcp", ":9090")
    server := grpc.NewServer()
    user.RegisterUserServiceServer(server, &userService{})
    server.Serve(listener)
}

Fase 2: Migração gradual de clientes

typescript// Serviços internos migram para cliente gRPC
const grpcClient = new UserServiceClient('localhost:9090');
// Clientes externos continuam usando REST
const restClient = axios.create({ baseURL: 'http://localhost:8080' });

Fase 3: Depreciar REST

  • Remove quando migração interna completa
  • Mantenha apenas para clientes externos

De REST para GraphQL

Fase 1: Adicionar camada GraphQL

typescript// Criar schema GraphQL baseado em endpoints REST existentes
const typeDefs = gql`
  type User {
    id: ID!
    email: String!
    name: String!
  }

  type Query {
    user(id: ID!): User
  }
`;

const resolvers = {
  Query: {
    user: async (_: any, { id }) => {
      // Delegar para backend REST existente
      const response = await fetch(`http://internal-api/users/${id}`);
      return response.json();
    },
  },
};

Fase 2: Migrar clientes gradualmente

typescript// Começar com feature flags
if (featureFlags.useGraphQL) {
  const result = await graphqlClient.query({ query: GET_USER });
} else {
  const response = await fetch(`/api/users/${userId}`);
}

Fase 3: Implementação GraphQL direta

  • Substitui delegação REST com queries diretas ao banco de dados
  • Remove endpoints REST onde não são mais necessários

Monitoramento e observabilidade

Observabilidade gRPC

go// Adicionar instrumentação OpenTelemetry
import (
    "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
)

server := grpc.NewServer(
    grpc.ChainUnaryInterceptor(
        otelgrpc.UnaryServerInterceptor(),
        loggingInterceptor(),
    ),
)

Observabilidade GraphQL

typescript// Rastrear complexidade de query e tempo de execução
const schema = makeExecutableSchema({ typeDefs, resolvers });

addMocksToSchema({ schema, mocks });

const apolloServer = new ApolloServer({
  schema,
  plugins: [
    new ApolloServerPluginLandingPageDisabled(),
    {
      requestDidStart: () => ({
        didResolveOperation: ({ request }) => {
          metrics.track('graphql.query', {
            operationName: request.operationName,
            complexity: calculateComplexity(request.query),
          });
        },
      }),
    },
  ],
});

Conclusão

A escolha correta de protocolo depende de restrições específicas: requisitos de performance, ecossistema de cliente, capacidades da equipe e complexidade operacional.

A maioria das organizações bem-sucedidas usa os três, roteando baseado no contexto. O perigo é escolher um protocolo e usá-lo em toda parte—pureza arquitetural que ignora trade-offs práticos.

Pergunta de fechamento prática: Quais de suas APIs atuais se beneficiariam de um protocolo diferente, e qual seria o custo de migração?

Fontes

Leituras relacionadas