Artículo original escrito por Daniel Newton
Artículo original An introduction to Reactive Relational Database Access with Spring and R2DBC
Traducido y adaptado por Josué

No hace mucho tiempo, se lanzó una variante reactiva del controlador JDBC, conocida como R2DBC.  Permite que los datos se transmitan de forma asincrónica a cualquier punto final que se haya suscrito a él.  Usando un controlador reactivo como R2DBC junto con Spring, WebFlux le permite escribir una aplicación completa que maneja la recepción y el envío de datos de forma asíncrona.

En esta publicación, nos centraremos en la base de datos, desde la conexión a la base de datos hasta finalmente guardar y recuperar datos.  Para hacer esto, usaremos Spring Data.  Al igual que con todos los módulos de Spring Data, nos proporciona una configuración lista para usar.  Esto disminuye la cantidad de código repetitivo que necesitamos escribir para configurar nuestra aplicación.  Además de eso, proporciona una capa sobre el controlador de la base de datos que hace que realizar las tareas simples sea más fácil y las tareas más difíciles sean un poco menos dolorosas.

Para el contenido de esta publicación, estoy haciendo uso de una base de datos de Postgres.  En el momento de escribir este artículo, solo Postgres, H2 y Microsoft SQL Server tienen sus propias implementaciones de controladores R2DBC.

Anteriormente, escribí dos publicaciones sobre bibliotecas Spring Data reactivas, una sobre Mongo y otra sobre Cassandra.  Es posible que hayas notado que ninguna de estas bases de datos son bases de datos RDBMS.  Ahora hay otros controladores reactivos disponibles desde hace mucho tiempo (escribí la publicación de Mongo hace casi 2 años), pero al momento de escribir un controlador reactivo para una base de datos RDBMS todavía es algo bastante nuevo.  Esta publicación seguirá un formato similar a aquellos.

Además, también he escrito un post sobre el uso de Spring Web Flux que mencioné en la introducción.

Dependencias

Hay algunas cosas que señalar aquí.

Cuanto más utilices Spring Boot, más te acostumbrarás a importar una única dependencia de spring-boot-starter para la cosa que quieras hacer. Por ejemplo, esperaba que hubiera una dependencia de spring-boot-starter-r2dbc, pero lamentablemente no la hay. Todavía.

Simplemente, esta biblioteca está en el lado más nuevo y en el momento de escribir, no tiene su propio módulo de Spring Boot que contiene las dependencias que necesita junto con una configuración más rápida a través de la configuración automática. Estoy seguro de que estas cosas llegarán en algún momento y harán que la configuración de un controlador R2DBC sea aún más fácil.

Por ahora, tendremos que rellenar manualmente algunas dependencias adicionales.‌‌‌‌

Además, las librerías R2DBC solo tienen versiones Milestone (una prueba más de que son nuevas) por lo que tenemos que asegurarnos de traer el repositorio Spring Milestone. Probablemente, tendré que actualizar este post en el futuro cuando consiga una versión de lanzamiento.

Conexión a la base de datos

Gracias a que Spring Data hace gran parte del trabajo por nosotros, el único Bean que hay que crear manualmente es el ConnectionFactory que contiene los detalles de conexión de la base de datos:

Lo primero que hay que observar aquí es la extensión de AbstractR2dbcConfiguration. Esta clase contiene un montón de Beans que ya no necesitamos crear manualmente. La implementación de connectionFactory es el único requisito de la clase, ya que es necesario para crear el Bean DatabaseClient. Este tipo de estructura es típica de los módulos de Spring Data, por lo que se siente bastante familiar al probar uno diferente. Además, yo esperaría que esta configuración manual se elimine una vez que la autoconfiguración esté disponible y se dirija únicamente a través del application.properties.

He incluido la propiedad del port aquí, pero si no has jugado con la configuración de tu Postgres entonces puedes confiar en el valor por defecto de 5432.

Las cuatro propiedades: host, database, username  y password definidas por el PostgresqlConnectionFactory son lo mínimo para que funcione. Cualquier cosa menos y experimentará excepciones durante el arranque.

Usando esta configuración, Spring es capaz de conectarse a una instancia de Postgres en ejecución.

El último dato a destacar de este ejemplo es el uso de @EnableR2dbcRepositories. Esta anotación indica a Spring que busque cualquier interfaz de repositorio que extienda la interfaz Repository de Spring. Esta se utiliza como interfaz base para instrumentar los repositorios de Spring Data. Veremos esto con más detalle en la siguiente sección. La principal información que debemos extraer de aquí es que debemos utilizar la anotación @EnableR2dbcRepositories  para aprovechar al máximo las capacidades de Spring Data.

Creación de un repositorio de datos de Spring

Como se ha mencionado anteriormente, en esta sección veremos cómo añadir un repositorio de datos de Spring. Estos repositorios son una buena característica de Spring Data, lo que significa que no es necesario escribir un montón de código extra para simplemente escribir una consulta.

Desafortunadamente, al menos por ahora, Spring R2DBC no puede inferir consultas de la misma manera que lo hacen actualmente otros módulos de Spring Data (estoy seguro de que esto se añadirá en algún momento). Esto significa que tendrá que utilizar la anotación @Query y escribir el SQL a mano. Echemos un vistazo:

Esta interfaz extiende R2dbcRepository. Este a su vez extiende ReactiveCrudRepository  y luego baja a Repository. ReactiveCrudRepository proporciona las funciones CRUD estándar y, por lo que tengo entendido, R2dbcRepository no proporciona ninguna función extra y es, en cambio, una interfaz creada para una mejor denominación de la situación.

R2dbcRepository toma dos parámetros genéricos, uno es la clase de entidad que toma como entrada y produce como salida. El segundo es el tipo de la clave primaria. Por lo tanto, en esta situación, la clase Person está siendo gestionada por el PersonRepository (tiene sentido) y el campo de la clave primaria dentro de Person es un Int.

Los tipos de retorno de las funciones de esta clase y los proporcionados por ReactiveCrudRepository son Flux y Mono (no se ven aquí). Estos son tipos del Proyecto Reactor que Spring utiliza como tipos de flujo reactivo por defecto. Flux representa un flujo de múltiples elementos mientras que Mono es un único resultado.

Finalmente, como mencioné antes del ejemplo, cada función está anotada con @Query. La sintaxis es bastante sencilla, siendo el SQL una cadena dentro de la anotación. El $1 ($2, $3, etc ... para más entradas) representa el valor introducido en la función. Una vez hecho esto, Spring se encargará del resto y pasará la(s) entrada(s) a su respectivo parámetro de entrada, recogerá los resultados y los asignará a la clase de entidad designada del repositorio.

Una mirada muy rápida a la entidad

No voy a decir mucho aquí, sino simplemente mostrar la clase Person utilizada por el PersonRepository.

En realidad, hay un punto que hacer aquí. id se ha hecho anulable y se ha proporcionado un valor por defecto de null para permitir que Postgres genere el siguiente valor adecuado por sí mismo. Si no es anulable y se proporciona un valor de id, Spring intentará ejecutar una actualización en lugar de una inserción al guardar. Hay otras formas de evitar esto, pero creo que esto es suficiente.

Esta entidad se asignará a la tabla de personas definida a continuación:

Ver todo en acción

Ahora veamos cómo se hace realmente. A continuación se muestra un código que inserta algunos registros y los recupera de diferentes maneras:

Una cosa que mencionaré sobre este código. Hay una posibilidad muy real de que se ejecute sin realmente insertar o leer algunos de los registros. Pero, si lo piensas, tiene sentido. Las aplicaciones reactivas están pensadas para hacer cosas de forma asíncrona y, por tanto, esta aplicación ha comenzado a procesar las llamadas a funciones en diferentes hilos. Sin bloquear el hilo principal, estos procesos asíncronos podrían no ejecutarse nunca completamente. Por esta razón, hay algunas llamadas a Thread.sleep en este código, pero las he eliminado del ejemplo para mantener todo ordenado.

El resultado de la ejecución del código anterior sería algo parecido a lo siguiente:

[ main] onSubscribe(FluxConcatMap.ConcatMapImmediate)[ main] request(unbounded)[actor-tcp-nio-1] onNext(Person(id=35, name=Dan Newton, age=25))[actor-tcp-nio-1] onNext(Person(id=36, name=Laura So, age=23))[actor-tcp-nio-1] onComplete()[actor-tcp-nio-2] findAll - Person(id=35, name=Dan Newton, age=25)[actor-tcp-nio-2] findAll - Person(id=36, name=Laura So, age=23)[actor-tcp-nio-4] findAllByName - Person(id=36, name=Laura So, age=23)[actor-tcp-nio-5] findAllByAge - Person(id=35, name=Dan Newton, age=25)

Hay que tener en cuenta algunas cosas:

  • onSubscribe y request ocurren en el hilo principal desde el que se llamó al Flux. Solo saveAll  produce esto, ya que ha incluido la función de log. Añadir esto a las otras llamadas habría llevado al mismo resultado de registro en el hilo principal.
  • La ejecución contenida en la función de suscripción y los pasos internos del Flux se ejecutan en hilos separados.

Esto no es ni de lejos una representación real de cómo usarías los Streams Reactivos en una aplicación real, pero esperamos que demuestre cómo usarlos y dé una idea de cómo se ejecutan.

Conclusión

En conclusión, los Reactive Streams han llegado a algunas bases de datos RDBMS gracias al driver R2DBC y a Spring Data que construye una capa encima para hacer todo un poco más ordenado. Usando Spring Data R2DBC somos capaces de crear una conexión a una base de datos y empezar a consultarla sin necesidad de demasiado código.

Aunque Spring ya está haciendo mucho por nosotros, podría hacer más. Actualmente, no tiene soporte para la autoconfiguración de Spring Boot. Lo cual es un poco molesto. Pero, estoy seguro de que alguien se pondrá a hacerlo pronto y hará que todo sea aún mejor de lo que ya es.

El código utilizado en este post se puede encontrar en mi GitHub.

Si te ha resultado útil este post, puedes seguirme en Twitter en @LankyDanDev para estar al día de mis nuevas publicaciones.

Publicado originalmente en lankydanblog.com el 16 de febrero de 2019.