38 points par GN⁺ 2025-12-05 | 10 commentaires | Partager sur WhatsApp
  • JSON, devenu le standard des API web, est facile à lire et flexible, mais présente des limites en matière de performance et de fiabilité
  • Protobuf (Protocol Buffers) garantit clairement la structure des données grâce à une définition de types stricte et à la génération automatique de code
  • Grâce à la sérialisation binaire, il réduit la taille des données d’environ 3 fois ou plus par rapport à JSON et améliore la vitesse de transfert
  • Le serveur et le client partagent le même schéma .proto, ce qui élimine les incompatibilités de types et le besoin de validations manuelles
  • Le débogage est plus difficile, mais en matière de performance, maintenabilité et efficacité de développement, Protobuf convient mieux aux API modernes

L’universalité et les limites de JSON

  • JSON est un format texte facile à lire par les humains : un simple console.log() suffit pour inspecter les données
  • Grâce à son intégration parfaite avec le web, il est largement adopté dans JavaScript comme dans les frameworks backend
  • Il offre une grande flexibilité, avec la possibilité d’ajouter ou supprimer des champs et de modifier les types, mais cela ouvre aussi la porte aux incohérences de structure et aux erreurs
  • Son écosystème d’outils est riche, et il se manipule facilement avec un éditeur de texte ou curl
  • Malgré ces avantages, il existe de meilleures alternatives en matière de performance et de sûreté des types

Vue d’ensemble de Protobuf

  • Un format de sérialisation binaire développé par Google en 2001 et rendu public en 2008
  • Largement utilisé dans les systèmes internes et pour la communication entre microservices
  • On pense souvent à tort qu’il faut l’utiliser avec gRPC, alors que Protobuf peut aussi être utilisé de manière autonome dans des API HTTP
  • Au départ, sa faible lisibilité due au format binaire le rendait moins accessible, mais il présente de forts avantages en matière d’efficacité et de fiabilité

Un système de types puissant et la génération de code

  • Protobuf définit clairement la structure des données via des fichiers .proto
    • Chaque champ possède un type strict, un identifiant numérique et un nom fixe
  • Exemple :
    message User {
      int32 id = 1;
      string name = 2;
      string email = 3;
      bool isActive = 4;
    }
    
  • La commande protoc prend en charge la génération automatique de code dans de nombreux langages, dont Dart, TypeScript, Kotlin, Swift, C#, Go et Rust
  • Le code généré permet d’effectuer la sérialisation (writeToBuffer) et la désérialisation (fromBuffer) sans validation ni parsing manuels
  • Au final, on gagne à la fois en temps et en maintenabilité

L’efficacité de la sérialisation binaire

  • Protobuf sérialise les données en binaire plutôt qu’en texte, ce qui le rend très compact et rapide
  • Comparaison de taille pour les mêmes données (objet User) :
    • JSON : 86 octets (68 octets sans espaces)
    • Protobuf : 30 octets
  • Cette efficacité s’explique par :
    • l’utilisation de l’encodage varint pour les nombres
    • l’usage de tags numériques au lieu de clés texte
    • la suppression des espaces et de la syntaxe superflue
    • l’optimisation des champs optionnels
  • Résultat : moins de bande passante consommée, temps de réponse amélioré, économie de données mobiles et meilleure expérience utilisateur

Exemple d’API Protobuf basée sur Dart

  • Le package shelf permet de mettre en place un serveur HTTP simple qui renvoie un objet User en Protobuf
  • Points clés du code serveur :
    • création d’un objet User(), puis sérialisation avec writeToBuffer()
    • définition de l’en-tête de réponse 'content-type': 'application/protobuf'
  • Le client utilise le package http et user.pb.dart pour décoder directement les données Protobuf
  • Comme le serveur et le client partagent le même schéma .proto, aucune incohérence de structure de données ne survient
  • La même approche s’applique aussi en Go, Rust, Kotlin, Swift, C#, TypeScript, etc.

Les avantages restants de JSON

  • Avec Protobuf, il est difficile d’interpréter le sens sans schéma
    • Au lieu de noms de champs, seuls des identifiants numériques apparaissent, ce qui le rend difficile à lire pour un humain
  • Exemple de comparaison :
    • JSON : { "id": 42, "name": "Alice" }
    • Protobuf : 1: 42, 2: "Alice"
  • Par conséquent, Protobuf nécessite :
    • des outils de décodage dédiés
    • une gestion du schéma et du versioning rigoureuse
  • Malgré cela, les gains en performance et en efficacité sont bien supérieurs

Conclusion

  • Protobuf est une technologie de sérialisation mature et haute performance, parfaitement exploitable même dans des API publiques
  • Il fonctionne de manière autonome dans une API HTTP classique, sans gRPC
  • C’est un outil qui améliore à la fois les performances, la robustesse, la réduction des erreurs et l’efficacité de développement
  • Il mérite clairement d’être envisagé dans les projets de nouvelle génération

10 commentaires

 
tested 2025-12-09
 
onixboox 2025-12-08

https://msgpack.org/ Qu'en pensez-vous ?

 
cosine20 2025-12-08

MessagePack aussi, c’est bien.

 
savvykang 2025-12-06

Je trouve contradictoire de prétendre qu’un format est mature alors qu’il n’existe même pas de décodeur officiel pour le débogage.

 
jjw9512151 2025-12-05

Comme tous les outils, ce n’est pas une solution universelle, mais je pense que Protobuf est aussi un très bon outil.
J’ai notamment eu un cas où il fallait envoyer, dans un environnement embarqué, des données volumineuses et très fréquentes (20 fois par seconde) à des clients dans différents langages, et nanopb m’a permis de faire ça proprement.

 
ifmkl 2025-12-05

À force d’être aussi strict, on ne va pas finir par recevoir du XML ? haha

 
click 2025-12-06

Si le schéma est aussi défini en dtd et mis en cache côté parseur, cela pourrait avoir pour effet de ne transmettre le schéma qu’une seule fois.

 
bakyeono 2025-12-05
  • Le format binaire idéal que j’imagine serait basé sur un schéma tout en incluant ce schéma dans le message. Ainsi, on pourrait le lire directement avec un plugin vim. Quand on traite des millions d’objets, ajouter un schéma de 1 KB à un message de 2 GB n’est pas une grosse contrainte
  • Mais dans les services web, c’est souvent l’inverse : le schéma fait 200 KB et le message 1 KB. Dans ce cas, c’est inefficace

=> De toute façon, il faut bien transmettre le schéma au moins une fois, non ? Même avec JSON, ce n’est pas qu’il n’y a pas de schéma : il est simplement inclus implicitement dans les données, donc on ne peut pas vraiment dire qu’on ne transmet pas de schéma. Au contraire, on le transmet de façon redondante pour chaque champ, ce qui est encore plus inefficace. Une approche « basée sur un schéma tout en incluant le schéma dans le message » me semble plutôt intéressante.

 
GN⁺ 2025-12-05
Avis Hacker News
  • Avec JSON, on envoie souvent des données ambiguës ou non garanties. Divers problèmes apparaissent : champs manquants, erreurs de type, fautes de frappe dans les clés, structures non documentées, etc. Un billet affirmait pourtant qu’avec Protobuf, cela devenait impossible grâce à une définition claire de la structure des messages via des fichiers .proto. Mais c’est une mauvaise compréhension de la philosophie de Protobuf. Dans proto3, les champs required ne sont tout simplement pas pris en charge. La documentation officielle (Protobuf Best Practices) indique même explicitement que « les champs required ont été supprimés car ils sont nuisibles ». En fin de compte, les clients Protobuf doivent eux aussi être écrits de manière défensive, comme les API JSON

    • Ce blog contient beaucoup de malentendus similaires. Par exemple, dans un article opposé à l’usage de SVG, il ne tient pas compte de l’avantage du redimensionnement libre des formats vectoriels
    • Le cœur du problème, ce sont seulement les différences entre langages et implémentations client/serveur. J’utilise le framework Gooey côté client en m’appuyant sur le concept de marshalling en Go. Si on dépasse les limites de Go, on peut l’utiliser de manière très type-safe. En revanche, il est important de bloquer les champs privés avec json:"-". Mon projet est visible sur Gooey
    • Cet article confond le format de sérialisation et la notion de contrat (contract)
    • Dans les systèmes réseau, le problème de désalignement des données (skew) existe toujours, quel que soit le mode d’encodage. En revanche, Protobuf fournit un objet à typage statique après décodage. On peut aussi valider JSON, mais la plupart du temps ce n’est pas fait. Au final, les objets JSON sont transformés dans tous les sens et plus personne ne peut vraiment être sûr de leur structure interne
    • L’auteur du billet original voulait sans doute simplement dire que, dans Protobuf, les champs manquants sont initialisés avec leur valeur par défaut. Ce n’est pas la même chose que la notion de champ « required »
  • Le JSON compressé est tout à fait utilisable, et son coût initial de communication est faible. Bien sûr, quand des champs disparaissent ou que les types changent, cela pose problème, mais la plupart des gens qui essaient de concevoir une structure parfaitement typée et de mettre en place un processus de synchronisation des versions échouent. Au final, c’est l’option au plus faible coût humain qui l’emporte. C’est pour cela que JSON ne disparaîtra pas tant qu’une alternative avec un coût de communication humain plus faible n’apparaîtra pas

    • Exact. La plupart des architectes n’envisagent pas proto sauf en cas de besoin clair, comme gRPC. JSON ne sera pas remplacé tant qu’il n’existera pas une alternative qu’on puisse déboguer directement avec console.log()
    • Le débogage est aussi un point fort de JSON. Il suffit de l’ouvrir et de le lire. À l’inverse, Protobuf nécessite du tooling
    • C’est vrai. Mais les gens préfèrent souvent éviter 15 minutes d’effort supplémentaire au moment de la conception, quitte à passer ensuite 3 mois à remonter les problèmes
    • JSON ne disparaîtra sans doute jamais complètement, comme COBOL, mais dans les nouveaux projets, il n’y a pas vraiment de raison de l’utiliser
  • Protobuf n’est pas parfait. Si le serveur et le client sont déployés à des moments différents et que les versions du schéma diffèrent, la sûreté disparaît. On peut atténuer cela en interdisant la réutilisation des ID, en recopiant les unknown fields, etc., mais les systèmes distribués sont complexes par nature. Malgré tout, protobuf3 a résolu beaucoup de problèmes de protobuf2. Avant, on ne pouvait pas distinguer une valeur par défaut explicitement définie d’un champ manquant, mais aujourd’hui, l’usage du type message permet de résoudre cela

    • Que ce soit avec JSON ou Protobuf, il faut imposer des tests de compatibilité de version dans le pipeline CI pour rester en sécurité
    • Aucun système de types ne survit intact à un passage sur le réseau
  • Le texte parlait de « très haute efficacité », mais sans mentionner gzip. La plupart des données textuelles sont déjà transmises avec compression automatique. Il faut donc comparer Protobuf à du JSON compressé avec gzip

    • J’ai moi aussi testé plusieurs formats binaires, mais au final, c’est le JSON gzippé qui s’est révélé de loin le plus efficace
    • Le principal défaut de JSON, c’est la vitesse de sérialisation/désérialisation. Pour le reste, les problèmes peuvent être résolus progressivement
    • Le JSON/HTML en streaming avec Brotli ou zstd mérite aussi d’être envisagé. On peut tirer parti de la fenêtre de compression pendant la durée de la connexion
    • Référence connexe : comparatif de performance Protobuf par Auth0
    • L’association de JSON et mod_deflate produit une différence très sensible en pratique
  • Défendre un meilleur protocole, c’est très bien, mais il est difficile de dire que Protobuf remplace JSON à la fois en efficacité et en ergonomie. À cause de son schéma strict, Protobuf rate des usages où JSON excelle. CBOR est au contraire plus adapté comme remplaçant de JSON. CBOR conserve la souplesse de JSON tout en proposant un encodage plus compact

    • Mais le schéma strict de Protobuf peut aussi être un avantage. La plupart des API ne publient pas de schéma JSON. J’ai déjà validé avec ajv ou superstruct, mais avec Protobuf, ce n’est pas nécessaire
    • Ce serait bien que les navigateurs prennent en charge une API CBOR directement. L’implémentation interne existe déjà, donc cela ne devrait pas être très difficile
  • En 1984, ASN.1 faisait déjà ce que fait Protobuf, avec davantage de souplesse. Avec l’encodage DER, ce n’est même pas si mauvais. Il suffit de regarder cet exemple ASN.1 DER. Protobuf est trop complexe par rapport à ce qu’il accomplit

    • ASN.1 a trop de fonctionnalités. Si on les prend toutes en charge, on obtient une bibliothèque excessivement complexe, et si on n’en prend qu’une partie, ce n’est plus vraiment de l’ASN.1 standard
    • Moi, je préfère ASN.1 DER. J’ai publié en FOSS un encodeur/décodeur DER que j’ai implémenté moi-même en C. J’ai créé une extension « ASN.1X » pour couvrir complètement le modèle de données de JSON
    • Mais dans des systèmes comme SNMP, la souplesse excessive d’ASN.1 a justement posé problème. Chaque fabricant l’étendait à sa façon
    • Même chez Google, la sérialisation/désérialisation Protobuf consommait beaucoup de CPU
    • ASN.1 est surconçu (overengineered), donc difficile à prendre en charge. Des fonctionnalités comme l’héritage sont inutiles
  • J’ai construit tout un système de production avec Protobuf, et la maintenance était en elle-même pénible. Techniquement, cela paraît séduisant, mais en pratique, JSON est bien plus simple

    • La lisibilité et la facilité de débogage de JSON ne doivent pas être sous-estimées. La plupart des équipes choisissent JSON pour leur efficacité à court terme
    • Je serais curieux de savoir quels problèmes se sont posés. D’après mon expérience, le risque de corruption des données est bien plus élevé avec JSON que les désagréments de Protobuf. Protobuf déclenche des erreurs de compilation, alors que JSON casse en production
  • Protobuf est excellent, mais c’est dommage qu’il ne prenne pas en charge le zero-copy. Des formats comme Cap’n Proto éliminent le goulet d’étranglement de la sérialisation/désérialisation

    • Mais en pratique, le zero-copy peut au contraire être plus lent. Copier en cache ne coûte presque rien, tandis que manipuler directement des structures dynamiques ajoute du surcoût. Dans la plupart des cas, une seule copie (one-copy) suffit largement
    • C’est surtout un argument marketing de Cap’n Proto ; en pratique, la différence de performance est minime. Les deux formats ont besoin de convertir types natifs ↔ binaire. Selon le payload, les performances sont comparables
    • Ce n’est peut-être pas un problème du format, mais de l’implémentation de la bibliothèque
  • Dans un projet NodeJS, j’ai défini toute l’API en .proto et construit un serveur qui répond en proto ou en JSON selon le Content-Type. C’est bien plus structuré que Swagger. En revanche, c’est dommage que Google n’ait pas fourni cette fonctionnalité dans une bibliothèque officielle. gRPC est peu pratique à cause de sa dépendance à HTTP/2. Au passage, je pense que le Text proto est le meilleur langage de configuration statique

    • Pour cet usage, Twirp convient bien. Il gère Protobuf ou JSON sur un simple HTTP
    • ConnectRPC propose une approche similaire. En revanche, l’étendue de sa prise en charge reste encore floue
  • Le format binaire dont je rêve serait basé sur un schéma tout en intégrant ce schéma dans le message lui-même. Ainsi, on pourrait le lire directement avec un plugin vim. Quand on manipule des millions d’objets, ajouter 1 KB de schéma à un message de 2 GB n’est pas une grosse contrainte

    • Il existe déjà chez Google tout un écosystème Protobuf avec schéma embarqué. Riegeli peut servir de référence
    • Avro ou Yardl proposent aussi une approche similaire
    • Mais dans les services web, on a souvent au contraire un schéma de 200 KB pour un message de 1 KB. Dans ce cas, c’est inefficace
    • Avro reste malgré tout une bonne alternative
 
vipeen 2025-12-06

« Le débogage est difficile, mais »

Rejeté