December 9, 2022

Branches Tech

Engagé pour la qualité technologique

Comment sérialiser efficacement les données avec des tampons de protocole – Grape Up

6 min read
Comment sérialiser efficacement les données avec des tampons de protocole - Grape Up

Dans un monde de microservices, nous devons souvent transmettre des informations entre les applications. Nous sérialisons les données dans un format qui peut être récupéré des deux côtés. L’une des solutions de sérialisation est Protocol Buffers (Protobuf) – le mécanisme indépendant de la langue de Google. Les messages peuvent être interprétés par un récepteur utilisant la même langue ou une langue différente de celle d’un producteur. De nombreux langages sont pris en charge, tels que Java, Go, Python et C++.

Une structure de données est définie à l’aide d’un langage neutre par .proto des dossiers. Le fichier est ensuite compilé en code pour être utilisé dans les applications. Il est conçu pour la performance. Les tampons de protocole encodent les données au format binaire, ce qui réduit la taille des messages et améliore la vitesse de transmission.

Définition du format des messages

Cette .proto le fichier représente les informations de géolocalisation d’un véhicule donné.

1 syntax = "proto3";
2
3 package com.grapeup.geolocation;
4
5 import "google/type/latlng.proto";
6 import "google/protobuf/timestamp.proto";
7
8 message Geolocation 
9  string vin = 1;
10  google.protobuf.Timestamp occurredOn = 2;
11  int32 speed = 3;
12  google.type.LatLng coordinates = 4;
13
1 syntax = "proto3";

La syntaxe fait référence à la version de Protobuf, elle peut être proto2 ou proto3.

1package com.grapeup.geolocation;

La déclaration de package évite les conflits de nommage entre différents projets.

1 message Geolocation 
2  string vin = 1;
3  google.protobuf.Timestamp occurredOn = 2;
4  int32 speed = 3;
5  google.type.LatLng coordinates = 4;
6

La définition de message contient un nom et un ensemble de champs typés. Des types de données simples sont disponibles, tels que bool, int32, double, string, etc. Vous pouvez également définir vos propres types ou les importer.

1google.protobuf.Timestamp occurredOn = 2;

La = 1, = 2 les marqueurs identifient l’étiquette unique. Les balises sont une représentation numérique du champ et sont utilisées pour identifier le champ dans le format binaire du message. Ils doivent être uniques dans un message et ne doivent pas être modifiés une fois que le message est utilisé. Si un champ est supprimé d’une définition déjà utilisée, il doit être reserved.

Types de champs

Outre les types scalaires, il existe de nombreuses autres options de type lors de la définition des messages. En voici quelques-unes, mais vous pouvez toutes les trouver dans le Guide des langues Guide des langues (proto3) | Tampons de protocole | Développeurs Google .

Types bien connus

1 import "google/type/latlng.proto";
2 import "google/protobuf/timestamp.proto";
3
4 google.protobuf.Timestamp occurredOn = 2;
5 google.type.LatLng coordinates = 4;

Il existe des types prédéfinis disponibles à utiliser Aperçu | Tampons de protocole | Développeurs Google . Ils sont connus sous le nom de Well Know Types et doivent être importés dans .proto .

LatLng représente une paire de latitude et de longitude.

Timestamp est un point précis dans le temps avec une précision de l’ordre de la nanoseconde.

Types personnalisés

1 message SingularSearchResponse 
2  Geolocation geolocation = 1;
3

Vous pouvez utiliser votre type personnalisé comme champ dans une autre définition de message.

Listes

1 message SearchResponse 
2  repeated Geolocation geolocations = 1;
3

Vous pouvez définir des listes en utilisant un mot-clé répété.

Un des

Il peut arriver que dans un message il n’y ait toujours qu’un seul ensemble de champs. Dans ce cas, TelemetryUpdate contiendra des informations de géolocalisation, de kilométrage ou de niveau de carburant.

Ceci peut être réalisé en utilisant oneof. Définir la valeur sur l’un des champs effacera tous les autres champs définis dans oneof.

1 message TelemetryUpdate 
2  string vin = 1;
3  oneof update 
4    Geolocation geolocation = 2;
5    Mileage mileage =3;
6    FuelLevel fuelLevel = 4;
7  
8
9
10 message Geolocation 
11  ...
12
13
14 message Mileage 
15  ...
16
17
18 message FuelLevel 
19  ...
20

Gardez à l’esprit la rétrocompatibilité lors de la suppression de champs. Si vous recevez un message avec oneof qui a été retiré de .proto définition, il ne définira aucune des valeurs. Ce comportement revient à ne pas définir de valeur en premier lieu.

Vous pouvez effectuer différentes actions en fonction de la valeur définie à l’aide de la getUpdateCase() méthode.

1 public Optional<Object> getTelemetry(TelemetryUpdate telemetryUpdate) 
2        Optional<Object> telemetry = Optional.empty();
3        switch (telemetryUpdate.getUpdateCase()) 
4            case MILEAGE -> telemetry = Optional.of(telemetryUpdate.getMileage());
5            case FUELLEVEL -> telemetry = Optional.of(telemetryUpdate.getFuelLevel());
6            case GEOLOCATION -> telemetry = Optional.of(telemetryUpdate.getGeolocation());
7            case UPDATE_NOT_SET -> telemetry = Optional.empty();
8        
9        return telemetry;
10    

Les valeurs par défaut

Dans proto3 les champs de format auront toujours une valeur. Grâce à ça proto3 peut avoir une taille plus petite car les champs avec des valeurs par défaut sont omis de la charge utile. Cependant, cela pose un problème – pour les champs de message scalaires, il n’y a aucun moyen de savoir si un champ a été explicitement défini sur la valeur par défaut ou pas défini du tout.

Dans notre exemple, la vitesse est un champ facultatif – certains modules d’une voiture peuvent envoyer des données de vitesse, d’autres non. Si nous ne définissons pas la vitesse, l’objet de géolocalisation aura une vitesse avec la valeur par défaut définie sur 0. Ce n’est pas la même chose que de ne pas avoir de vitesse définie sur les messages.

Afin de gérer les valeurs par défaut, vous pouvez utiliser des types de wrapper officiels protobuf/wrappers.proto à main · protocolbuffers/protobuf . Ils permettent de faire la distinction entre absence et défaut. Au lieu d’avoir un type simple, nous utilisons Int32Value, qui est un wrapper pour le type scalaire int32.

1 import "google/protobuf/wrappers.proto";
2
3 message Geolocation 
4  google.protobuf.Int32Value speed = 3;
5

Si nous ne fournissons pas de vitesse, elle sera réglée sur nil.

Configurer avec Gradle

Une fois que vous avez défini vos messages, vous pouvez utiliser protoc, un compilateur de tampon de protocole, pour générer des classes dans un langage choisi. La classe générée peut ensuite être utilisée pour créer et récupérer des messages.

Afin de compiler en code Java, nous devons ajouter une dépendance et un plugin dans build.gradle

1 plugins 
2    id 'com.google.protobuf' version '0.8.18'
3
4
5 dependencies 
6    implementation 'com.google.protobuf:protobuf-java-util:3.17.2'
7

et configurez le compilateur. Pour les utilisateurs de Mac, une version spécifique d’osx doit être utilisée.

1 protobuf 
2    protoc 
3        if (osdetector.os == "osx") 
4            artifact = "com.google.protobuf:protoc:$protobuf_version:osx-x86_64"
5         else 
6            artifact = "com.google.protobuf:protoc:$protobuf_version"
7        
8    
9

Le code sera généré à l’aide de generateProto tâche.

Le code sera situé dans build/generated/source/proto/main/java dans un emballage tel que spécifié dans .proto dossier.

Nous devons également indiquer à gradle où se trouve le code généré

1 sourceSets 
2    main 
3        java 
4            srcDirs 'build/generated/source/proto/main/grpc'
5            srcDirs 'build/generated/source/proto/main/java'
6        
7    
8

La classe générée contient toutes les méthodes nécessaires pour construire le message ainsi que pour récupérer les valeurs des champs.

1 Geolocation geolocation = Geolocation.newBuilder()
2            .setCoordinates(LatLng.newBuilder().setLatitude(1.2).setLongitude(1.2).build())
3            .setVin("1G2NF12FX2C129610")
4            .setOccurredOn(Timestamp.newBuilder().setSeconds(12349023).build())
5            .build();
6
7 LatLng coordinates = geolocation.getCoordinates();
8 String vin = geolocation.getVin();

Tampons de protocole – Résumé

Comme indiqué, les tampons de protocole sont facilement configurables. Le mécanisme est indépendant du langage, et il est facile de partager le même .proto définition à travers différents microservices.

Protobuf est facilement couplé avec gRPC, où les méthodes peuvent être définies dans .proto fichiers et générés avec gradle.

Une documentation officielle est disponible Tampons de protocole | Développeurs Google et guides Guide des langues (proto3) | Tampons de protocole | Développeurs Google .

Leave a Reply