38 pontos por GN⁺ 2025-12-05 | 10 comentários | Compartilhar no WhatsApp
  • JSON, que se consolidou como padrão para APIs web, é fácil de ler e flexível, mas tem limitações em termos de desempenho e confiabilidade
  • Protobuf (Protocol Buffers) garante com clareza a estrutura dos dados por meio de definições de tipos rígidas e geração automática de código
  • Ao usar serialização binária, ele reduz o tamanho dos dados em mais de 3 vezes e melhora a velocidade de transmissão em comparação com JSON
  • Como servidor e cliente compartilham o mesmo esquema .proto, não há incompatibilidade de tipos nem necessidade de validação manual
  • Embora a depuração seja mais difícil, o Protobuf é mais adequado para APIs modernas em termos de desempenho, manutenibilidade e eficiência de desenvolvimento

A universalidade e os limites do JSON

  • JSON é um formato de texto fácil para humanos lerem, e dá para inspecionar os dados até com um simples console.log()
  • Graças à sua integração perfeita com a web, ele foi amplamente adotado em JavaScript e nos frameworks de backend em geral
  • Ele oferece a flexibilidade de adicionar ou remover campos e alterar tipos livremente, mas isso também abre espaço para inconsistências estruturais e erros
  • Como conta com um ecossistema de ferramentas rico, é fácil de manipular até só com um editor de texto ou curl
  • Ainda assim, apesar dessas vantagens, existem alternativas melhores quando o assunto é desempenho e segurança de tipos

Visão geral do Protobuf

  • Um formato de serialização binária desenvolvido pelo Google em 2001 e lançado publicamente em 2008
  • Amplamente usado em sistemas internos e na comunicação entre microsserviços
  • Muitas vezes há o equívoco de que ele precisa ser usado junto com gRPC, mas o Protobuf também pode ser usado de forma independente em APIs HTTP
  • No início, sua adoção era menor por causa da baixa visibilidade do formato binário, mas ele se destaca em eficiência e confiabilidade

Sistema de tipos forte e geração de código

  • O Protobuf define claramente a estrutura dos dados por meio de arquivos .proto
    • Cada campo tem um tipo rígido, um identificador numérico e um nome fixo
  • Exemplo:
    message User {
      int32 id = 1;
      string name = 2;
      string email = 3;
      bool isActive = 4;
    }
    
  • O comando protoc oferece geração automática de código para várias linguagens, como Dart, TypeScript, Kotlin, Swift, C#, Go e Rust
  • Com o código gerado, é possível fazer serialização (writeToBuffer) e desserialização (fromBuffer) sem necessidade de parsing ou validação manual
  • Como resultado, é possível ganhar economia de tempo e melhor manutenibilidade ao mesmo tempo

Eficiência da serialização binária

  • O Protobuf serializa os dados como binário em vez de texto, o que o torna muito compacto e rápido
  • Comparação de tamanho para os mesmos dados (objeto User):
    • JSON: 86 bytes (68 bytes sem espaços)
    • Protobuf: 30 bytes
  • Motivos da eficiência:
    • uso de codificação varint para números
    • uso de tags numéricas em vez de chaves de texto
    • remoção de espaços e sintaxe desnecessária
    • otimização de campos opcionais
  • Na prática, isso resulta em economia de banda, melhor tempo de resposta, redução do uso de dados móveis e melhora da experiência do usuário

Exemplo de API Protobuf em Dart

  • Usando o pacote shelf, é possível montar um servidor HTTP simples que retorna um objeto User em Protobuf
  • Pontos principais do código do servidor:
    • criar um objeto User() e serializá-lo com writeToBuffer()
    • definir 'content-type': 'application/protobuf' no cabeçalho da resposta
  • O cliente pode usar o pacote http e user.pb.dart para decodificar diretamente os dados Protobuf
  • Como servidor e cliente compartilham o mesmo esquema .proto, não ocorre divergência na estrutura dos dados
  • O mesmo padrão pode ser aplicado da mesma forma em Go, Rust, Kotlin, Swift, C#, TypeScript e outras linguagens

As vantagens que ainda restam ao JSON

  • No Protobuf, é difícil interpretar o significado sem um esquema
    • como aparecem apenas identificadores numéricos em vez de nomes de campos, fica difícil para humanos lerem
  • Comparação de exemplo:
    • JSON: { "id": 42, "name": "Alice" }
    • Protobuf: 1: 42, 2: "Alice"
  • Por isso, o Protobuf exige:
    • ferramentas dedicadas de decodificação
    • gestão de esquema e versionamento obrigatórios
  • Mesmo assim, os ganhos de desempenho e eficiência são muito maiores

Conclusão

  • O Protobuf é uma tecnologia de serialização madura e de alto desempenho, e pode ser usado sem problemas também em APIs públicas
  • Funciona de forma independente em APIs HTTP comuns, mesmo sem gRPC
  • É uma ferramenta que melhora desempenho, robustez, redução de erros e eficiência de desenvolvimento
  • Vale muito a pena considerar a adoção de Protobuf em projetos de próxima geração

10 comentários

 
GN⁺ 2025-12-05
Comentários do Hacker News
  • JSON muitas vezes acaba enviando dados ambíguos ou não garantidos. Podem surgir vários problemas, como campos ausentes, erros de tipo, typos nas chaves e estruturas não documentadas. Houve um texto defendendo que o Protobuf torna isso impossível ao definir claramente a estrutura das mensagens com arquivos .proto. Mas isso interpreta mal a filosofia do Protobuf. No proto3, campos required nem são suportados. A documentação oficial (Protobuf Best Practices) afirma explicitamente que “required fields were harmful and removed”. No fim, clientes em Protobuf também precisam ser escritos de forma defensiva, como APIs JSON

    • Esse blog tem vários mal-entendidos parecidos. Por exemplo, em um texto contra o uso de SVG, ele não considera a vantagem de escalonamento livre de formatos vetoriais
    • O centro do problema é só a diferença entre linguagens e implementações de cliente/servidor. Eu uso o framework Gooey no cliente aproveitando o conceito de Marshalling do Go. Superando as limitações do Go, dá para usar de forma bem type-safe. Só é importante bloquear campos privados com json:"-". Meu projeto pode ser visto em Gooey
    • Este texto está confundindo formato de serialização com o conceito de contrato (Contract)
    • Em sistemas de rede, o problema de inconsistência de dados (skew) sempre existe, independentemente da forma de codificação. Ainda assim, o Protobuf fornece objetos de tipo estático após a decodificação. JSON também pode ser validado, mas na maioria das vezes isso não é feito. No fim, os objetos JSON vão sendo transformados de um lado para o outro, e ninguém mais consegue ter certeza da estrutura interna
    • Talvez o autor do texto original só quisesse dizer que, no Protobuf, campos ausentes são inicializados com valores padrão. Isso é diferente do conceito de campo “required”
  • JSON comprimido é bom o bastante e tem baixo custo inicial de comunicação. Claro, há problemas quando faltam campos ou os tipos mudam, mas quase todo mundo que tenta projetar uma estrutura perfeitamente tipada e criar um processo para sincronização de versões acaba fracassando. No fim, vence a opção com menor custo humano. Por isso, o JSON não vai desaparecer até surgir uma alternativa com custo de comunicação humana ainda menor

    • Exato. A maioria dos arquitetos nem considera proto se não houver uma necessidade clara como gRPC. Enquanto não surgir uma alternativa que dê para depurar direto com console.log(), o JSON não será substituído
    • Depuração também é um ponto forte do JSON. É só abrir e ler. Já o Protobuf exige tooling
    • É verdade. Mas as pessoas preferem deixar para revisitar problemas por 3 meses depois, em vez de investir mais 15 minutos na fase de design
    • JSON não vai desaparecer totalmente, como COBOL, mas em projetos novos não há muito motivo para usá-lo
  • Protobuf não é perfeito. Quando servidor e cliente são implantados em momentos diferentes e as versões da especificação divergem, a segurança se quebra. Dá para mitigar isso com proibição de reutilização de IDs, cópia de unknown fields etc., mas sistemas distribuídos são complexos por natureza. Ainda assim, o protobuf3 resolveu muitos problemas do protobuf2. Antes não dava para distinguir se um valor padrão tinha sido definido ou se estava ausente; agora isso pode ser resolvido com o tipo message

    • Seja com JSON ou Protobuf, só é seguro quando testes de compatibilidade entre versões são obrigatórios no pipeline de CI
    • Qualquer sistema de tipos quebra quando atravessa a rede
  • O texto fala em “super eficiência”, mas não menciona gzip. A maior parte dos dados textuais já é transmitida com compressão automática. Portanto, o Protobuf deveria ser comparado com JSON com gzip

    • Eu também testei vários formatos binários, mas no fim JSON com gzip foi disparado o mais eficiente
    • O ponto fraco do JSON é a velocidade de serialização/desserialização. O resto pode ser resolvido gradualmente
    • Vale considerar também JSON/HTML com streaming em Brotli/zstd. Dá para aproveitar a janela de compressão enquanto a conexão permanece aberta
    • Referência relacionada: post da Auth0 comparando desempenho de Protobuf
    • A combinação de JSON com mod_deflate gera uma diferença percebida enorme
  • Defender protocolos melhores é válido, mas é difícil dizer que o Protobuf substitui o JSON tanto em eficiência quanto em usabilidade. Por causa do esquema rígido, o Protobuf perde justamente áreas em que o JSON vai bem. Na verdade, CBOR seria um substituto mais adequado para o JSON. O CBOR mantém a flexibilidade do JSON com uma codificação mais compacta

    • Mas o esquema rígido do Protobuf também pode ser uma vantagem. A maioria das APIs nem publica um esquema JSON. Eu já validei com ajv e superstruct, mas com Protobuf isso nem é necessário
    • Seria ótimo se o navegador suportasse API de CBOR diretamente. A implementação interna já existe, então não seria difícil
  • O ASN.1 de 1984 já fazia o que o Protobuf faz, de forma mais flexível. Com codificação DER, isso nem fica tão ruim. Basta ver este exemplo de ASN.1 DER. O Protobuf é complexo demais para o que entrega

    • O ASN.1 tem funcionalidades demais. Se você suportar tudo, vira uma biblioteca excessivamente complexa; se suportar só parte, então já não é mais ASN.1 padrão
    • Eu prefiro ASN.1 DER. Publiquei como FOSS um codificador/decodificador DER que implementei diretamente em C. Também criei uma extensão “ASN.1X” que inclui completamente o modelo de dados do JSON
    • Mas em sistemas como SNMP, a flexibilidade excessiva do ASN.1 acabou sendo um problema. Cada fabricante fazia suas próprias extensões
    • Até dentro do Google, serialização/desserialização de Protobuf consumia muito CPU
    • ASN.1 é overengineered e difícil de suportar. Recursos como herança são desnecessários
  • Eu montei um sistema inteiro de produção com Protobuf e a gestão em si foi dolorosa. Tecnicamente parece bom, mas na prática o JSON é muito mais simples

    • A legibilidade e a facilidade de depuração do JSON não podem ser subestimadas. A maioria das equipes escolhe JSON pela eficiência de curto prazo
    • Fiquei curioso sobre quais problemas surgiram. Pela minha experiência, o inconveniente do Protobuf é menor do que o risco de corrupção de dados no JSON. No Protobuf, isso aparece como erro de compilação; no JSON, explode em produção
  • Protobuf é excelente, mas é uma pena não ter suporte a zero-copy. Formatos como Cap’n Proto eliminam o gargalo de serialização/desserialização

    • Mas na prática zero-copy pode até ser mais lento. Copiar dentro do cache é quase de graça, mas lidar diretamente com estruturas dinâmicas gera overhead. Na maioria dos casos, uma única cópia (one-copy) já basta
    • Isso é mais uma alegação do marketing do Cap’n Proto; na prática, a diferença de desempenho é mínima. Nos dois formatos ainda é preciso converter entre tipos nativos e binário. Dependendo do payload, o desempenho é parecido
    • Isso talvez não seja problema do formato, e sim da implementação da biblioteca
  • Em um projeto NodeJS, eu defini a API inteira em .proto e criei um servidor que responde com proto ou JSON conforme o Content-Type. É muito mais estruturado do que Swagger. Só acho uma pena que o Google não tenha oferecido isso como biblioteca oficial. O gRPC é incômodo por depender de HTTP/2. E, aliás, acho que Text proto é a melhor linguagem de configuração estática

    • Para esse objetivo, Twirp parece adequado. Ele lida com Protobuf ou JSON sobre HTTP simples
    • O ConnectRPC também oferece uma abordagem parecida. Só que o alcance do suporte ainda não está claro
  • O formato binário dos meus sonhos seria baseado em esquema, mas incluiria o esquema dentro da própria mensagem. Assim daria para ler direto com um plugin de vim. Ao lidar com milhões de objetos, anexar um esquema de 1 KB a uma mensagem de 2 GB não pesa tanto

    • Dentro do Google já existe um ecossistema de Protobuf com esquema embutido. Vale olhar o Riegeli
    • Avro e Yardl também seguem uma linha parecida
    • Mas em serviços web, às vezes acontece o contrário: o esquema tem 200 KB e a mensagem 1 KB. Aí isso fica ineficiente
    • Avro continua sendo uma boa alternativa
 
tested 2025-12-09
 
onixboox 2025-12-08

https://msgpack.org/ O que acha disso?

 
cosine20 2025-12-08

MessagePack também é bom.

 
savvykang 2025-12-06

Acho contraditório afirmar que um formato sem sequer um decodificador oficial para depuração seja maduro.

 
vipeen 2025-12-06

"Mas depurar é difícil"

Reprovado

 
jjw9512151 2025-12-05

Como toda ferramenta, não existe solução universal, mas acho que o Protobuf também é uma ferramenta boa o suficiente.
Especialmente quando precisei enviar dados volumosos e de alta frequência (20 vezes por segundo) para vários idiomas/clientes em um ambiente embarcado, consegui fazer isso de forma bem limpa com o nanopb.

 
ifmkl 2025-12-05

Se ficar tão rígido assim, não vai acabar vindo em XML? haha

 
click 2025-12-06

Se o esquema também for definido em DTD e armazenado em cache no lado do parser, também seria possível obter o efeito de transmitir o esquema apenas uma vez.

 
bakyeono 2025-12-05
  • O formato binário que eu idealizo seria baseado em esquema, mas incluiria o esquema dentro da própria mensagem. Assim, daria para ler direto com um plugin do vim. Ao lidar com milhões de objetos, anexar um esquema de 1 KB a uma mensagem de 2 GB não é um grande custo
  • Mas, em serviços web, muitas vezes acontece o contrário: o esquema tem 200 KB e a mensagem 1 KB. Nesse caso, é ineficiente

=> De qualquer forma, o esquema não precisa necessariamente ser transmitido pelo menos uma vez? Mesmo no JSON, não é que não exista esquema; ele só está implicitamente incluído nos dados, então não acho que seja correto dizer que o esquema não é transmitido. Na verdade, como o esquema é retransmitido de forma redundante em cada item, acaba sendo ainda mais ineficiente. Esse formato "baseado em esquema, mas com o esquema incluído na própria mensagem" parece bem interessante.