Exploremos cómo implementar gRPC en Java
gRPC (Google Remote Procedure Call): gRPC es una arquitectura RPC de código abierto desarrollada por Google para permitir la comunicación de alta velocidad entre microservicios. gRPC permite a los desarrolladores integrar servicios escritos en diferentes lenguajes. gRPC utiliza el formato de mensajería Protobuf (Protocol Buffers), un formato de mensajería muy eficiente y de gran empaquetado para serializar datos estructurados.
Para algunos casos de uso, la API gRPC puede ser más eficiente que la API REST
Intentamos escribir un servidor en gRPC. En primer lugar, necesitamos escribir varios archivos .proto
que describen servicios y modelos (DTO). Para un servidor sencillo, utilizaremos ProfileService y ProfileDescriptor
ProfileService tiene el siguiente aspecto
sintaxis = "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) devuelve (stream ProfileDescriptor) {}
rpc biDirectionalStream (stream ProfileDescriptor) devuelve (stream ProfileDescriptor) {}
}
gRPC soporta una gran variedad de opciones de comunicación cliente-servidor. Las desglosaremos todas
- Llamada normal al servidor - solicitud/respuesta.
- Streaming de cliente a servidor.
- Streaming de servidor a cliente.
- Y, por supuesto, el flujo bidireccional.
El servicio ProfileService utiliza el ProfileDescriptor, que se especifica en la sección de importación
sintax = "proto3";
package com.deft.grpc;
message ProfileDescriptor {
int64 profile_id = 1;
string name = 2;
}
- int64 es Long para Java. Pertenece al id del perfil.
- Cadena - al igual que en Java, se trata de una cadena variable.
Puede utilizar Gradle o maven para construir el proyecto. Es más conveniente para mí utilizar maven. Y más adelante será el código utilizando maven. Esto es importante decirlo porque para Gradle, la futura generación del .proto será ligeramente diferente, y el archivo de construcción tendrá que ser configurado de manera diferente. Para escribir un simple servidor gRPC, sólo necesitamos una dependencia
<dependencia>
<groupId>io.github.lognet</groupId>
<artifactId>grpc-spring-boot-starter</artifactId>
<version>4.5.4</version>
</dependencia>
Es simplemente increíble. Este arrancador hace una enorme cantidad de trabajo por nosotros
El proyecto que vamos a crear tendrá este aspecto
Necesitamos GrpcServerApplication para iniciar la aplicación Spring Boot. Y GrpcProfileService, que implementará métodos del servicio .proto
. Para utilizar protoc y generar clases a partir de archivos .proto escritos, añada protobuf-maven-plugin a pom.xml. La sección build tendrá el siguiente aspecto
<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 - especifique el directorio donde se encuentran los archivos .proto.
- outputDirectory - seleccione el directorio donde se generarán los archivos.
- clearOutputDirectory - una bandera que indica no borrar los archivos generados.
En este punto, ya puede construir un proyecto. A continuación, debe ir a la carpeta que hemos especificado en el directorio de salida. Los archivos generados estarán allí. Ahora puede implementar gradualmente GrpcProfileService
La declaración de la clase tendrá este aspecto
@GRpcService
public class GrpcProfileService extends ProfileServiceGrpc.ProfileServiceImplBase
Anotación GRpcService - Marque la clase como un frijol grpc-servicio
Dado que heredamos nuestro servicio de PerfilServicioGrpc, ProfileServiceImplBase, podemos anular los métodos de la clase padre. El primer método que anularemos es getCurrentProfile
@Override
public void getCurrentProfile(Empty request, StreamObserver<ProfileDescriptorOuterClass.ProfileDescriptor> responseObserver) {
System.out.println("getCurrentProfile");
responseObserver.onNext(DescriptorDePerfilOuterClass.ProfileDescriptor
.newBuilder()
.setProfileId(1)
.setName("test")
.build());
responseObserver.onCompleted();
}
Para responder al cliente, debe llamar al método onNext en el StreamObserver pasado. Después de enviar la respuesta, envíe una señal al cliente de que el servidor ha terminado de trabajar onCompleted. Al enviar una solicitud al servidor getCurrentProfile, la respuesta será
{
"profile_id": "1",
"name":
“
test"
}
A continuación, echemos un vistazo al flujo del servidor. Con este enfoque de mensajería, el cliente envía una petición al servidor, el servidor responde al cliente con un flujo de mensajes. Por ejemplo, envía cinco peticiones en un bucle. Cuando finaliza el envío, el servidor envía un mensaje al cliente sobre la finalización satisfactoria del flujo
El método de flujo del servidor anulado tendrá el siguiente aspecto
@Override
public void serverStream(Solicitud vacía, StreamObserver<DescriptorDePerfilOuterClass.ProfileDescriptor> responseObserver) {
for (int i = 0; i < 5; i ) {
responseObserver.onNext(ProfileDescriptorOuterClass.ProfileDescriptor
.newBuilder()
.setProfileId(i)
.build());
}
responseObserver.onCompleted();
}
Así, el cliente recibirá cinco mensajes con un PerfilId, igual al número de respuesta
{
"profile_id": "0",
"name": ""
}
{
"profile_id": "1",
"name": ""
}
...
{
"profile_id": "4",
"name":
“”
}
El flujo cliente es muy similar al flujo servidor. Sólo que ahora el cliente transmite un flujo de mensajes y el servidor los procesa. El servidor puede procesar los mensajes inmediatamente o esperar a recibir todas las peticiones del cliente y luego procesarlas.
@Override
public StreamObserver<ProfileDescriptorOuterClass.ProfileDescriptor> clientStream(StreamObserver<Empty> responseObserver) {
return new StreamObserver<>() {
@Override
public void onNext(ProfileDescriptorOuterClass.ProfileDescriptor profileDescriptor) {
log.info("ProfileDescriptor del cliente. Profile id: {}", profileDescriptor.getProfileId());
}
@Override
public void onError(Throwable throwable) {
}
@Override
public void onCompleted() {
responseObserver.onCompleted();
}
};
}
En el flujo Cliente, debe devolver al cliente el StreamObserver al que el servidor recibirá los mensajes. Se llamará al método onError si se ha producido un error en el flujo. Por ejemplo, ha terminado incorrectamente
Para implementar un flujo bidireccional, es necesario combinar la creación de un flujo desde el servidor y el cliente
@Override
public StreamObserver<DescriptorDePerfilOuterClass.ProfileDescriptor> biDirectionalStream(
StreamObserver<DescriptorDePerfilOuterClass.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();
}
};
}
En este ejemplo, en respuesta al mensaje del cliente, el servidor devolverá un perfil con una cuenta de puntos aumentada
Conclusión
Hemos cubierto las opciones básicas para la mensajería entre un cliente y un servidor utilizando gRPC: flujo servidor implementado, flujo cliente, flujo bidireccional
El artículo fue escrito por Sergey Golitsyn