- 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
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
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. Noproto3, 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 JSONjson:"-". Meu projeto pode ser visto em GooeyJSON 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
console.log(), o JSON não será substituídoProtobuf 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
protobuf3resolveu muitos problemas doprotobuf2. 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 tipomessageO 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
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
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
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
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
Em um projeto NodeJS, eu defini a API inteira em
.protoe 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áticaO 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
TypeSpec
https://typespec.io/
https://msgpack.org/ O que acha disso?
MessagePack também é bom.
Acho contraditório afirmar que um formato sem sequer um decodificador oficial para depuração seja maduro.
"Mas depurar é difícil"
Reprovado
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.
Se ficar tão rígido assim, não vai acabar vindo em XML? haha
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.
=> 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.