Pourquoi j’ai arrêté d’utiliser JSON dans mon API pour passer à Protobuf
(aloisdeniel.com)- 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
protocprend 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
shelfpermet de mettre en place un serveur HTTP simple qui renvoie un objetUseren Protobuf - Points clés du code serveur :
- création d’un objet
User(), puis sérialisation avecwriteToBuffer() - définition de l’en-tête de réponse
'content-type': 'application/protobuf'
- création d’un objet
- Le client utilise le package
httpetuser.pb.dartpour 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"
- JSON :
- 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
TypeSpec
https://typespec.io/
https://msgpack.org/ Qu'en pensez-vous ?
MessagePack aussi, c’est bien.
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.
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.
À force d’être aussi strict, on ne va pas finir par recevoir du XML ? haha
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.
=> 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.
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. Dansproto3, 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 JSONjson:"-". Mon projet est visible sur GooeyLe 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
console.log()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,
protobuf3a résolu beaucoup de problèmes deprotobuf2. Avant, on ne pouvait pas distinguer une valeur par défaut explicitement définie d’un champ manquant, mais aujourd’hui, l’usage du typemessagepermet de résoudre celaLe 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
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
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
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
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
Dans un projet NodeJS, j’ai défini toute l’API en
.protoet 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 statiqueLe 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
« Le débogage est difficile, mais »
Rejeté