Automatización gRPC - Mirror Strategy
Introducción
gRPC es un framework de procedimiento remoto de alto rendimiento desarrollado por Google. Utiliza el protocolo HTTP/2 para la comunicación entre los servidores y los clientes, y permite la comunicación entre los servicios de diferentes lenguajes de programación.
Configuración del entorno
En el archivo build.gradle
se añadieron las dependencias necesarias de gRPC
testImplementation 'io.grpc:grpc-testing:1.41.0'
testImplementation group: 'com.google.protobuf', name: 'protobuf-java', version:'3.23.2'
implementation 'io.grpc:grpc-stub:1.53.0'
implementation 'io.grpc:grpc-api:1.53.0'
implementation 'io.grpc:protoc-gen-grpc-java:1.53.0'
implementation 'io.grpc:grpc-core:1.53.0'
implementation 'io.grpc:grpc-netty-shaded:1.53.0'
implementation 'com.google.api.grpc:proto-google-common-protos:2.14.1'
implementation 'javax.annotation:javax.annotation-api:1.3.2'
implementation 'io.grpc:grpc-protobuf:1.55.1'
Archivos .proto
En gRPC, los servicios y los mensajes que se envían entre el cliente y el servidor se definen en archivos .proto
, que utilizan el lenguaje de definición de interfaz (IDL) de Protocol Buffers. Aquí es donde se definen las operaciones que el servicio puede realizar y los tipos de datos que se envían y reciben. Esencialmente, es la especificación de la API de gRPC.
Agregar los archivos .proto
necesarios para las pruebas de servicios en src/main/proto/servicename
ejemplo:
Stubs
Un "stub" se refiere al cliente de un servicio gRPC que se conecta a un servidor. Es esencialmente código generado a partir del archivo .proto
que un cliente puede usar para invocar métodos de un servicio remoto. Toma un mensaje de protocolo, serializa el mensaje en el formato correcto, envía la solicitud a través de la red, y luego espera y deserializa las respuestas para enviarlas de vuelta al código de llamada.
El enfoque más común para realizar pruebas en Java con gRPC es utilizar un stub generado automáticamente. Esto permite enfocarse en probar la lógica específica del cliente sin preocuparse por la complejidad de la comunicación subyacente.
Generar los stubs (archivos Java) a partir del archivo .proto:
Gradle posee un plugin que permite generar los archivos Java, los cuales incluyen el código para los servicios y mensajes a partir de los archivos .proto
, utilizando el compilador de Protocol Buffers (protobuf-protoc).
apply
plugin
'com.google.protobuf'
protobuf {
protoc {
artifact = 'com.google.protobuf:protoc:3.23.2'
}
plugins{
grpc{
artifact = 'io.grpc:protoc-gen-grpc-java:1.53.0'
}
}
generateProtoTasks {
all()*.plugins {
grpc{
}
}
}
}
Para que el plugin compile los archivos .proto
se ejecuta el comando gradle build
y se generan los archivos Java necesarios.
Los stubs serán generados automáticamente en el directorio build/generated/source/proto/main/grpc
.
Tipos de Stubs en gRPC
Blocking (síncrono)
Realiza llamadas síncronas. Esto significa que cuando se hace una llamada a un método en el stub, la ejecución de ese hilo se bloquea hasta que se recibe la respuesta del servidor. Este comportamiento es útil en situaciones en las que se necesita esperar la respuesta del servidor antes de continuar con la ejecución del programa, como cuando se está realizando una operación de consulta o actualización de datos.
Non-blocking (Future stub)
Las llamadas son no bloqueantes, lo que significa que la llamada devuelve inmediatamente un Future
que el cliente puede usar para obtener la respuesta cuando esté disponible.
Async (asíncrono)
Las llamadas son asincrónicas. El cliente debe pasar un StreamObserver
a la llamada que será llamado cuando la respuesta esté disponible.
Después de generar el código del cliente y del servidor, se implementan los stubs en los script de prueba.
Creación del tipo de Stub
En gRPC en necesario especificar el tipo de stub (cliente) que que se requiere usar para hacer una petición. El tipo de stub que se elija, determina cómo se maneja la petición.
Para seleccionar el tipo de stub, se deben usar los métodos correspondientes de la clase generada automáticamente a partir del .proto
.
Por ejemplo, si el servicio se llama AccountAggregationService
, gRPC generará una clase AccountAggregationServiceGrpc
que contiene métodos: newBlockingStub
, newFutureStub
, y newStub
que se pueden usar para crear los distintos tipos de stubs.
A continuación se especifica como se crea un stub de gRPC bloqueante para la interfaz AccountAggregationService
. Esto es esencialmente un cliente que se usará para hacer llamadas al servicio.
Implementación de Clases Base
ServiceBase.java
ServiceBase.java es una clase abstracta genérica que proporciona la estructura y herramientas para configurar una conexión al servicio y comunicarse con él utilizando la librería de gRPC para la creación de canales y stubs.
Al iniciar una prueba, la clase se conecta a un servicio usando una dirección URL (
url
).Una vez establecida la conexión, crea un "stub" o punto de conexión para comunicarse con el servicio.
Al finalizar una prueba, cierra la conexión.
La razón principal para hacer la clase abstracta es que contiene un método abstracto (buildStub
) que las clases hijas deben implementar obligatoriamente.
T
es un tipo genérico. La clase está diseñada para trabajar con cualquier tipo T
que extienda (o sea un subtipo de) AbstractStub<T>
.
buildStub
tiene el propósito de construir (o inicializar) un "stub". Es abstracto, no tiene una implementación en esta clase ya que las clases que hereden de esta deben proporcionarla.
Al ser T
es un tipo que extiende de AbstractStub<T>
. El método buildStub
devolverá un objeto de tipo T
, que será una especie de AbstractStub
.
(ManagedChannel channel)
: El método recibirá un parámetro llamado channel
de tipo ManagedChannel
El cual representa una conexión a un servidor gRPC.
Estos métodos se utilizan para configurar y limpiar recursos antes y después de cada prueba individual.
setup()
:
Crea un canal gRPC para comunicarse con un servicio remoto a la url que será proporcionada por la clase que herede de
ServiceBase
.Añade un interceptor de registro. (Para imprimir en el log: Calling URL, Calling method, Sending message y Received response)
Construye un "stub" bloqueante utilizando el canal recién creado.
tearDown()
:
Verifica si el canal existe.
Si existe, cierra el canal inmediatamente y espera hasta 5 segundos para que se cierre completamente.
AccountAggregationServiceBase.java
Esta clase es específico está parametrizada con AccountAggregationServiceGrpc.AccountAggregationServiceBlockingStub
para el tipo genérico T
.
Es decir, MirrorStrategyServiceBase
trabaja específicamente con el "stub bloqueante" del servicio AccountAggregationServiceGrpc
.
Cuando se crea una instancia de esta clase, automáticamente se establece su URL al valor de baseMirrorStrategyADDRESS
usando el método setUrl
.
El tag @Override
indica que ese método está sobrescribiendo al método de la clase en ServiceBase
. Aquí ya se está proporcionando una implementación específica del método buildStub
, recibe el canal creado para posteriormente crear un objeto stub bloqueante con el canal obtenido y lo devuelve de tipo AccountAggregationServiceGrpc.AccountAggregationServiceBlockingStub
esto ya permitirá hacer las llamadas a procedimientos remotos en el @Before
de la clase ServiceBase
.
Construcción Test Cases
ServiceDetailsPositiveTests.java
Esta clase es un ejemplo de una clase de prueba para un servicio es especifico y hereda de MirrorStrategyServiceBase
.
En esencia, esta clase está estructurada de la misma forma que en los test del proyecto de REST.
Contiene las notaciones de método de prueba y de la descripción del caso (@Tesy
y @Title
)
Construcción de la solicitud:
Se crea un objeto request de tipo
AccountDetailsRequestDTO
(generado por el compilador de Protocol Buffers-protoc
).Después se usa el método
newBuilder()
para iniciar la construcción del mensaje.
La función
setAccountNumber()
establece el número de cuenta obtenido de dataProperties
Finalmente,
build()
completa y devuelve una instancia completamente construida del mensaje.
Envío de la solicitud GRPC:
Se realiza la solicitud GRPC usando el método
performGRPCRequest
de la clasePerform
.Se le pasa la solicitud que se creó en el paso anterior.
Se utiliza una lambda
req -> blockingStub.getAccountDetails(req)
para especificar cómo debe realizarse la solicitud. Esto indica que hay una instancia deblockingStub
que tiene un métodogetAccountDetails
para realizar la acción deseada.
Validación:
Se valida el estado de la respuesta utilizando el método
statusCodeIs
de la claseValidate
.Se comprueba si el último código de estado (obtenido a través de
Perform.latestStatusCode
) es "OK", lo que indica una respuesta exitosa.
Envío del mensaje
Perform.java
Esta clase define una forma estructurada de realizar llamadas a GRPC y manejar errores de manera uniforme.
@FunctionalInterface
es una anotación que indica que la interfaz tiene un método abstracto y puede servir como tipo objetivo para una expresión lambda.GRPCCall<S>
es la interfaz funcional que define un métodoexecute
, que toma un argumento S (la solicitud) y devuelve un objeto de tipoMessage
Este es el método principal que se usa en la clase de los tests. Su propósito principal es ejecutar una llamada gRPC usando la función (call
) con la solicitud dada (request
), convierte la respuesta Protobuf a JSON y la guarda en response
, actualizalatestStatusCode
a "OK".
Si se produce una excepción StatusRuntimeException
, el método extrae la información detallada del error usando StatusProto.fromThrowable()
y ErrorInfo
. Luego, crea un objeto JSON (dataError
) que contiene los metadatos de error y lo guarda en response
, además de registrar el error, actualiza latestStatusCode
con el código de estado del error.
Validaciones de la respuesta
Validate.java
Contiene un conjunto de métodos para realizar diferentes tipos de validaciones en respuestas, obtenidas a través de llamadas gRPC.
Ejecución y resultados de las pruebas:
@WithTag
El uso del tag WithTag
es una forma de categorizar y filtrar pruebas específicas que queremos ejecutar.
La opción -Ptags
del comando Gradle permite que sólo se ejecuten pruebas que coincidan con las etiquetas especificadas.
Comando de ejecución
Para realizar una ejecución de los scripts se usa el comando Gradle.
Si se requiere ejecutar pruebas específicas, se pueden utilizar la etiqueta antes mencionada.
./gradlew test -Ptags=etiqueta-relacionada-con-grpc