Tiempo de lectura: 3 minutos

¿Qué vamos a hacer?

Esta pequeña entrada no pretende ser un tutorial ni de Micronaut, ni de RxJava ni de MongoDB, sino más bien hablar sobre alguno de los beneficios que nos aporta la programación reactiva y su manera elegante de tratar eventos asíncronos.

Usando programación reactiva conseguimos que los eventos que normalmente son bloqueantes o se manejan de una manera oscura con callbacks, etc. puedan tratarse se una manera elegante y sencilla, con ello evitamos bloqueos I/O y el temido callback hell.

Así, cuando hablemos de programación reactiva debemos tener en cuenta 2 conceptos: Asíncrono + No-bloqueante.

Hoy vamos a darle un poco de uso en el backend usando Micronaut, RxJava y el driver reactivo de MongoDB para la JVM.

¿Beneficios? Veamos un par de ejemplos para entender el poder de este paradigma.

Usando Fandango

Para las pruebas utilizaremos Fandango (https://github.com/gloin-dev/Fandango), una API para almacenar/recuperar/editar imágenes y otros ficheros binarios.

Vamos a explicar el “problema” primero para ponernos en contexto.

Cuando Fandango recibe una nueva imagen, aparte de almacenarla y devolver un id para poder referenciarla en un futuro, genera un thumbnail de esa misma imagen que se referencia con el mismo id. Los pasos que sigue son:

  1. Subir foto
  2. Generar imagen y thumbnail
  3. Almacenar imagen y thumbnail
  4. Devolver id

Nos vamos a centrar en el punto 3: Almacenar imagen y thumbnail

Método bloqueante

public Single<String> processImageUploadBlocking(CompletedFileUpload completedFileUpload) {
    // Build the image
    Image image = imageManager.buildImage(completedFileUpload);
    // Build the thumbnail
    Thumbnail thumbnail = imageManager.buildThumbnail(image);
    // Save the image
    Image savedImage = imageRepository
            .saveImage(image)
            .blockingGet();
    // Save the thumbnail
    Thumbnail savedThumbnail = thumbnailRepository
            .saveThumbnail(thumbnail)
            .blockingGet();
    // Return the Single
    return Single.just(savedImage.getId().toString());
}

Si nos damos cuenta en el código, básicamente se generan la imagen y el thumbnail y, al ser bloqueantes, primero se guardará la imagen y justo después el thumbnail, por lo que el tiempo total será:

Tiempo total = Tiempo de guardar imagen + Tiempo de guardar thumbnail

He comprobado el tiempo de ejecución realizando 3 pruebas con la misma imagen:

Blocking execution time in millis: 481
Blocking execution time in millis: 447
Blocking execution time in millis: 440

Método no bloqueante

public Single<String> processImageUpload(CompletedFileUpload completedFileUpload) throws IOException {
    // Build the image
    Image image = imageManager.buildImage(completedFileUpload);
    // Build the thumbnail
    Thumbnail thumbnail = imageManager.buildThumbnail(image);
    // Save the image
    Single<Image> savedImage = imageRepository.saveImage(image);
    // Save the thumbnail
    Single<Thumbnail> savedThumbnail = thumbnailRepository.saveThumbnail(thumbnail);
    // Combine both Operations
    return Single.zip(
            savedImage,
            savedThumbnail,
            (outputImage, outputSavedThumbnail) -> outputImage.getId().toString()
    );
}

En este caso, se hace lo mismo que en el código anterior pero usando programación reactiva.

Si nos damos cuenta, los repository respectivos nos devuelven un objeto Single, que implementa el patrón observable para una única respuesta.

¿Qué significa esto? Que ambos métodos se ejecutan sin esperar a tener lista la persistencia de su objeto, dando paso a la ejecución del siguiente método.

La magia está cuando llegamos al método Single.zip (http://reactivex.io/documentation/operators/zip.html), que de una manera elegante espera a que todos los Single puedan ser mergeados y tengamos nuestros objetos listos para dar un resultado (en este caso el id).

El beneficio es claro: en vez de encadenar nuestras operaciones sobre la base de datos, conseguimos hacerlas en paralelo, por lo que el tiempo total de guardado será el tiempo de ejecución de la operación mayor, y no la suma de todas. Tras 3 ejecuciones, obtenemos estos tiempos:

Non-Blocking Execution time in millis: 405
Non-Blocking Execution time in millis: 398
Non-Blocking Execution time in millis: 401

Conclusiones

Comparando estos resultados con los anteriores, podemos inferir que imagen tarda en guardarse ~400 ms y el thumbnail ~40 ms, teniendo en cuenta que el resto de operaciones son constantes para la misma imagen.

Si con un ejemplo tan sencillo, con solamente dos operaciones y siendo una mucho más costosa que la otra, ya conseguimos una ganancia considerable, es fácil imaginar las ventajas de la programación reactiva en escenarios más complejos.

Ricardo Flores

Software arquebusier doing cool things in @GloinSL. Linux lover ∞ retro garbage necromancer