- JSON, ya establecido como estándar de las API web, es fácil de leer y flexible, pero tiene límites en rendimiento y estabilidad
- Protobuf (Protocol Buffers) garantiza con claridad la estructura de los datos mediante definiciones de tipos estrictas y generación automática de código
- Al usar serialización binaria, reduce el tamaño de los datos en más de 3 veces y mejora la velocidad de transferencia frente a JSON
- Como el servidor y el cliente comparten el mismo esquema
.proto, no hay desajustes de tipos ni necesidad de validación manual
- Aunque depurarlo es más difícil, Protobuf encaja mejor en las API modernas en términos de rendimiento, mantenibilidad y eficiencia de desarrollo
La universalidad de JSON y sus límites
- JSON es un formato de texto fácil de leer para humanos, y se pueden revisar los datos incluso con un simple
console.log()
- Gracias a su integración perfecta con la web, ha sido ampliamente adoptado en JavaScript y en los frameworks de backend
- Ofrece la flexibilidad de agregar o quitar campos y cambiar tipos con libertad, pero eso también puede provocar desajustes de estructura o errores
- Tiene un ecosistema de herramientas muy amplio, por lo que se puede manejar fácilmente incluso solo con un editor de texto o
curl
- Pero, pese a todas esas ventajas, existen alternativas mejores en rendimiento y seguridad de tipos
Visión general de Protobuf
- Un formato de serialización binaria desarrollado por Google en 2001 y publicado en 2008
- Se usa ampliamente en sistemas internos y en la comunicación entre microservicios
- A menudo existe la idea equivocada de que debe usarse junto con gRPC, pero Protobuf también puede utilizarse por sí solo en API HTTP
- Al principio resultaba menos accesible por la falta de visibilidad del formato binario, pero destaca mucho en eficiencia y estabilidad
Sistema de tipos sólido y generación de código
Eficiencia de la serialización binaria
- Protobuf se serializa como datos binarios en lugar de texto, por lo que es muy compacto y rápido
- Comparación de tamaño con los mismos datos (objeto
User):
- JSON: 86 bytes (68 bytes sin espacios)
- Protobuf: 30 bytes
- Motivos de esa eficiencia:
- Usa codificación varint para los números
- Usa etiquetas numéricas en lugar de claves de texto
- Elimina espacios y sintaxis innecesaria
- Optimización de campos opcionales
- El resultado es ahorro de ancho de banda, mejor tiempo de respuesta, menor consumo de datos móviles y mejor experiencia de usuario
Ejemplo de API Protobuf basada en Dart
- Se configura un servidor HTTP simple con el paquete
shelf y se devuelve el objeto User en Protobuf
- Puntos clave del código del servidor:
- Se crea un objeto
User() y luego se serializa con writeToBuffer()
- En los headers de respuesta se especifica
'content-type': 'application/protobuf'
- El cliente usa el paquete
http y user.pb.dart para decodificar directamente los datos Protobuf
- Como el servidor y el cliente comparten el mismo esquema
.proto, no se producen discrepancias en la estructura de datos
- El mismo enfoque puede aplicarse de igual forma en Go, Rust, Kotlin, Swift, C#, TypeScript y otros lenguajes
Ventajas que aún conserva JSON
- Con Protobuf es difícil interpretar el significado sin un esquema
- Como se muestran identificadores numéricos en lugar de nombres de campos, es difícil de leer para humanos
- Comparación de ejemplo:
- JSON:
{ "id": 42, "name": "Alice" }
- Protobuf:
1: 42, 2: "Alice"
- Por eso, Protobuf:
- Requiere herramientas de decodificación especializadas
- Hace indispensable la gestión de esquemas y de versiones
- Aun así, las ventajas en rendimiento y eficiencia son mucho mayores
Conclusión
- Protobuf es una tecnología de serialización madura y de alto rendimiento, y puede usarse perfectamente incluso en API públicas
- Funciona de forma independiente en una API HTTP común, incluso sin gRPC
- Es una herramienta que mejora el rendimiento, la solidez, la reducción de errores y la eficiencia de desarrollo
- Vale completamente la pena considerar adoptar Protobuf en proyectos de próxima generación
10 comentarios
Opiniones en Hacker News
JSON a menudo termina enviando datos ambiguos o no garantizados. Surgen muchos problemas: campos faltantes, errores de tipo, typos en las claves, estructuras no documentadas, etc. Había un texto que afirmaba que Protobuf hace imposible esto al definir claramente la estructura de los mensajes con archivos
.proto. Pero eso malinterpreta la filosofía de Protobuf. Enproto3, los campos required ni siquiera están soportados. La documentación oficial (Protobuf Best Practices) incluso dice explícitamente que “los campos required eran dañinos y fueron eliminados”. Al final, los clientes de Protobuf también deben escribirse de forma defensiva, igual que una API JSONjson:"-". Mi proyecto puede verse en GooeyJSON comprimido es bastante usable y tiene un costo inicial de comunicación bajo. Claro, si faltan campos o cambian los tipos hay problemas, pero la mayoría de quienes intentan diseñar estructuras perfectamente tipadas y crear procesos para sincronizar versiones fracasan. Al final, gana la opción con menor costo humano. Por eso JSON no va a desaparecer hasta que aparezca un sustituto con un costo de comunicación humana todavía menor
console.log()Protobuf no es perfecto. Si el servidor y el cliente se despliegan en momentos distintos y las versiones de la especificación no coinciden, la seguridad se rompe. Se puede mitigar evitando reutilizar IDs y copiando unknown fields, pero los sistemas distribuidos son complejos por naturaleza. Aun así,
protobuf3resolvió muchos problemas deprotobuf2. Antes no se podía distinguir si un valor por defecto había sido establecido o si faltaba; ahora eso se resuelve usando el tipomessageEl texto habla de “súper eficiencia”, pero no menciona gzip. La mayoría de los datos de texto ya se transmiten comprimidos automáticamente. Por eso Protobuf debería compararse con JSON comprimido con gzip
Está bien defender protocolos mejores, pero es difícil decir que Protobuf reemplaza a JSON tanto en eficiencia como en usabilidad. Protobuf deja fuera áreas donde JSON funciona bien por su esquema estricto. Más bien, CBOR parece más adecuado como reemplazo de JSON. CBOR es tan flexible como JSON, pero con una codificación más compacta
ASN.1 de 1984 ya hace lo que hace Protobuf, y con más flexibilidad. Si usas codificación DER, no está nada mal. Basta ver este ejemplo de ASN.1 DER. Protobuf es demasiado complejo para lo que logra
Yo construí todo un sistema de producción con Protobuf, y administrarlo fue doloroso. Técnicamente se ve bien, pero en la práctica JSON es mucho más simple
Protobuf es excelente, pero es una lástima que no soporte zero-copy. Formatos como Cap’n Proto eliminan el cuello de botella de la serialización/deserialización
En un proyecto de NodeJS definí toda la API con
.protoe hice un servidor que responde con proto o JSON según el Content-Type. Es mucho más estructurado que Swagger. Solo da pena que Google no haya ofrecido esto como librería oficial. gRPC es incómodo por su dependencia de HTTP/2. Por cierto, creo que Text proto es el mejor lenguaje de configuración estáticaEl formato binario con el que sueño sería basado en esquema, pero incluyendo el esquema dentro del mensaje. Así se podría leer directamente con un plugin de vim. Si manejas millones de objetos, agregar un esquema de 1 KB a un mensaje de 2 GB no es una gran carga
TypeSpec
https://typespec.io/
¿Qué tal esto?
MessagePack también está bien.
Me parece contradictorio afirmar que un formato es maduro cuando ni siquiera tiene un decodificador oficial para depuración.
"Pero depurar es difícil"
Descartado
Como todas las herramientas, no es una solución universal, pero creo que Protobuf también es una herramienta bastante buena.
En particular, hubo una vez en que tuve que enviar datos de gran volumen y alta frecuencia (20 veces por segundo) a varios clientes en distintos lenguajes dentro de un entorno embebido, y lo resolví de forma limpia con nanopb.
Si te pones tan estricto, ¿no terminaría llegando en XML? jaja
Si el esquema también se define con DTD y se usa caché del lado del parser, entonces también se podría lograr el efecto de enviar el esquema solo una vez.
=> De todos modos, ¿no hay que transmitir el esquema obligatoriamente al menos una vez? Incluso con JSON no es que no haya esquema; más bien está incluido implícitamente en los datos, así que no creo que realmente se esté evitando transmitirlo. De hecho, es más ineficiente porque se transmite el esquema de forma redundante en cada campo. La idea de un formato "basado en esquemas, pero que incluya el esquema dentro del mensaje" suena bastante bien.