Java gRPC à partir de zéro

Publié: 2021-07-16

Explorons comment implémenter gRPC en Java.

gRPC (Google Remote Procedure Call) : gRPC est une architecture RPC open source développée par Google pour permettre une communication à haut débit entre les microservices. gRPC permet aux développeurs d'intégrer des services écrits dans différentes langues. gRPC utilise le format de messagerie Protobuf (Protocol Buffers), un format de messagerie hautement efficace et hautement compressé pour la sérialisation des données structurées.

Pour certains cas d'utilisation, l'API gRPC peut être plus efficace que l'API REST.

Essayons d'écrire un serveur sur gRPC. Tout d'abord, nous devons écrire plusieurs fichiers .proto qui décrivent les services et les modèles (DTO). Pour un serveur simple, nous utiliserons ProfileService et ProfileDescriptor.

ProfileService ressemble à ceci :

 syntax = "proto3"; package com.deft.grpc; import "google/protobuf/empty.proto"; import "profile_descriptor.proto"; service ProfileService { rpc GetCurrentProfile (google.protobuf.Empty) returns (ProfileDescriptor) {} rpc clientStream (stream ProfileDescriptor) returns (google.protobuf.Empty) {} rpc serverStream (google.protobuf.Empty) returns (stream ProfileDescriptor) {} rpc biDirectionalStream (stream ProfileDescriptor) returns (stream ProfileDescriptor) {} }

gRPC prend en charge une variété d'options de communication client-serveur. Nous allons tous les décomposer :

  • Appel serveur normal – demande/réponse.
  • Diffusion du client au serveur.
  • Diffusion du serveur au client.
  • Et, bien sûr, le flux bidirectionnel.

Le service ProfileService utilise le ProfileDescriptor, qui est spécifié dans la section d'importation :

 syntax = "proto3"; package com.deft.grpc; message ProfileDescriptor { int64 profile_id = 1; string name = 2; }
  • int64 est long pour Java. Laissez l'identifiant du profil appartenir.
  • Chaîne - tout comme en Java, il s'agit d'une variable de chaîne.

Vous pouvez utiliser Gradle ou maven pour créer le projet. Il est plus pratique pour moi d'utiliser maven. Et plus loin sera le code utilisant maven. C'est assez important pour le dire car pour Gradle, la future génération du .proto sera légèrement différente et le fichier de construction devra être configuré différemment. Pour écrire un serveur gRPC simple, nous n'avons besoin que d'une seule dépendance :

 <dependency> <groupId>io.github.lognet</groupId> <artifactId>grpc-spring-boot-starter</artifactId> <version>4.5.4</version> </dependency>

C'est juste incroyable. Ce démarreur fait un énorme travail pour nous.

Le projet que nous allons créer ressemblera à ceci :

Nous avons besoin de GrpcServerApplication pour démarrer l'application Spring Boot. Et GrpcProfileService, qui implémentera les méthodes du service .proto . Pour utiliser protoc et générer des classes à partir de fichiers .proto écrits, ajoutez protobuf-maven-plugin à pom.xml. La section de construction ressemblera à ceci :

 <build> <extensions> <extension> <groupId>kr.motd.maven</groupId> <artifactId>os-maven-plugin</artifactId> <version>1.6.2</version> </extension> </extensions> <plugins> <plugin> <groupId>org.xolstice.maven.plugins</groupId> <artifactId>protobuf-maven-plugin</artifactId> <version>0.6.1</version> <configuration> <protoSourceRoot>${project.basedir}/src/main/proto</protoSourceRoot> <outputDirectory>${basedir}/target/generated-sources/grpc-java</outputDirectory> <protocArtifact>com.google.protobuf:protoc:3.12.0:exe:${os.detected.classifier}</protocArtifact> <pluginId>grpc-java</pluginId> <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.38.0:exe:${os.detected.classifier}</pluginArtifact> <clearOutputDirectory>false</clearOutputDirectory> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>compile-custom</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
  • protoSourceRoot – spécifiant le répertoire où se trouvent les fichiers .proto.
  • outputDirectory – sélectionnez le répertoire dans lequel les fichiers seront générés.
  • clearOutputDirectory – un indicateur indiquant de ne pas effacer les fichiers générés.

A ce stade, vous pouvez construire un projet. Ensuite, vous devez accéder au dossier que nous avons spécifié dans le répertoire de sortie. Les fichiers générés seront là. Vous pouvez maintenant implémenter progressivement GrpcProfileService .

La déclaration de classe ressemblera à ceci :

 @GRpcService public class GrpcProfileService extends ProfileServiceGrpc.ProfileServiceImplBase

Annotation GRpcService – Marque la classe comme un bean grpc-service.

Puisque nous héritons de notre service de ProfileServiceGrpc , ProfileServiceImplBase , nous pouvons remplacer les méthodes de la classe parent. La première méthode que nous remplacerons est getCurrentProfile :

 @Override public void getCurrentProfile(Empty request, StreamObserver<ProfileDescriptorOuterClass.ProfileDescriptor> responseObserver) { System.out.println("getCurrentProfile"); responseObserver.onNext(ProfileDescriptorOuterClass.ProfileDescriptor .newBuilder() .setProfileId(1) .setName("test") .build()); responseObserver.onCompleted(); }

Pour répondre au client, vous devez appeler la méthode onNext sur le StreamObserver passé. Après avoir envoyé la réponse, envoyez un signal au client que le serveur a fini de travailler surCompleted . Lors de l'envoi d'une requête au serveur getCurrentProfile, la réponse sera :

 { "profile_id": "1", "name": "test" }

Examinons ensuite le flux du serveur. Avec cette approche de messagerie, le client envoie une requête au serveur, le serveur répond au client avec un flux de messages. Par exemple, il envoie cinq requêtes en boucle. Lorsque l'envoi est terminé, le serveur envoie un message au client concernant la réussite du flux.

La méthode de flux de serveur remplacée ressemblera à ceci :

 @Override public void serverStream(Empty request, StreamObserver<ProfileDescriptorOuterClass.ProfileDescriptor> responseObserver) { for (int i = 0; i < 5; i++) { responseObserver.onNext(ProfileDescriptorOuterClass.ProfileDescriptor .newBuilder() .setProfileId(i) .build()); } responseObserver.onCompleted(); }

Ainsi, le client recevra cinq messages avec un ProfileId, égal au numéro de réponse.

 { "profile_id": "0", "name": "" } { "profile_id": "1", "name": "" } … { "profile_id": "4", "name": "" }

Le flux client est très similaire au flux serveur. Seulement maintenant, le client transmet un flux de messages et le serveur les traite. Le serveur peut traiter les messages immédiatement ou attendre toutes les demandes du client, puis les traiter.

 @Override public StreamObserver<ProfileDescriptorOuterClass.ProfileDescriptor> clientStream(StreamObserver<Empty> responseObserver) { return new StreamObserver<>() { @Override public void onNext(ProfileDescriptorOuterClass.ProfileDescriptor profileDescriptor) { log.info("ProfileDescriptor from client. Profile id: {}", profileDescriptor.getProfileId()); } @Override public void onError(Throwable throwable) { } @Override public void onCompleted() { responseObserver.onCompleted(); } }; }

Dans le flux Client, vous devez renvoyer le StreamObserver au client, auquel le serveur recevra les messages. La méthode onError sera appelée si une erreur s'est produite dans le flux. Par exemple, il s'est terminé de manière incorrecte.

Pour implémenter un flux bidirectionnel, il est nécessaire de combiner la création d'un flux depuis le serveur et le client.

 @Override public StreamObserver<ProfileDescriptorOuterClass.ProfileDescriptor> biDirectionalStream( StreamObserver<ProfileDescriptorOuterClass.ProfileDescriptor> responseObserver) { return new StreamObserver<>() { int pointCount = 0; @Override public void onNext(ProfileDescriptorOuterClass.ProfileDescriptor profileDescriptor) { log.info("biDirectionalStream, pointCount {}", pointCount); responseObserver.onNext(ProfileDescriptorOuterClass.ProfileDescriptor .newBuilder() .setProfileId(pointCount++) .build()); } @Override public void onError(Throwable throwable) { } @Override public void onCompleted() { responseObserver.onCompleted(); } }; }

Dans cet exemple, en réponse au message du client, le serveur renverra un profil avec un pointCount augmenté.

Conclusion

Nous avons couvert les options de base pour la messagerie entre un client et un serveur en utilisant gRPC : flux serveur implémenté, flux client, flux bidirectionnel.

L'article a été écrit par Sergey Golitsyn