Recientemente la programación reactiva (Reactive Programming) se ha ido ganando un lugar en la comunidad de desarrolladores y clientes gracias a las características que ofrece para construir aplicaciones de forma declarativa, mismas que hacen que nuestras aplicaciones sean mas sensibles y resilientes.
Spring 5 ha incorporado elementos de programación reactiva dentro de su implementación principal lo cual ha marcado un precedente en el cambio de paradigma hacia una programación mas declarativa.
Reactive Programming
En el concepto de programación reactiva se manejan flujos de datos asíncronos entre productores de datos y consumidores de datos en donde ambos necesitan reaccionar a esos datos de forma que no bloqueen los recursos. Entonces la característica principal de la programación reactiva es que no bloquea ningún recurso y se maneja a través de eventos asíncronos que requieren de pocos hilos para escalar.
Cuando hablamos de frameworks como spring que se basan en hilos (threads) hay una complejidad muy alta involucrada en escalar una aplicación que se basa en un estado compartido y mutable, hilos y locks.
En este contexto podemos decir que todo es un stream que actúa de forma que no bloquea cuando hay datos en el stream.
Tipos Reactivos En Spring
Spring tiene a partir de su versión 5 una implementación de tipos que heredan de estos streams reactivos e introduce 2 tipos reactivos principales:
Que son publishers del stream reactivo en cuestión.
Entonces si quisiéramos crear una aplicación web reactiva con spring y reactive-streams necesitaremos para ello 2 dependencias:
- spring–webflux
- reactive–stream
Que pueden ser declaradas en un pom como:
<dependency> <groupId>org.reactivestreams</groupId> <artifactId>reactive-streams</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webflux</artifactId> <version>5.0.0.RC2</version> </dependency>
Ejemplo
He creado una aplicación demo para explorar todos los conceptos de programación reactiva que spring ofrece. Pueden encontrar y descargar el repositorio aquí:
En el repositorio vemos la clase BookHandlerImpl.java que contiene lo siguiente:
package com.ricardogeek.handler; import com.ricardogeek.models.Book; import com.ricardogeek.repository.BookRepository; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.web.reactive.function.BodyInserters.fromObject; public class BookHandlerImpl implements BookHandler { private final BookRepository repositorio; public BookHandlerImpl(BookRepository repositorio) { this.repositorio = repositorio; } @Override public Mono<ServerResponse> getBookFromRepository(final ServerRequest request) { final int bookId = Integer.parseInt(request.pathVariable("id")); final Mono<ServerResponse> notFound = ServerResponse.notFound().build(); final Mono<Book> bookMono = this.repositorio.getLibro(bookId); return bookMono .flatMap(libro -> ServerResponse.ok().contentType(APPLICATION_JSON).body(fromObject(libro))) .switchIfEmpty(notFound); } @Override public Mono<ServerResponse> saveBookToRepository(final ServerRequest request) { final Mono<Book> libro = request.bodyToMono(Book.class); return ServerResponse.ok().build(this.repositorio.guardarLibro(libro)); } @Override public Mono<ServerResponse> getAllBooksFromRepository(final ServerRequest request) { final Flux<Book> libros = this.repositorio.getLibros(); return ServerResponse.ok().contentType(APPLICATION_JSON).body(libros, Book.class); } }
Como ven tenemos 3 operaciones en este handler (perdón por el spanglish) :
- Obtener un libro especifico getBookFromRepository
- Obtener la lista completa de libros getAllBooksFromRepository
- Guardar un libro nuevo a la lista saveBookToRepository
Luego esta clase se usa en la case Server para definir nuestras rutas que actúan de manera reactiva según el handler que se le especifica.
private RouterFunction<ServerResponse> routingFunction() { final BookRepository repositorio = new BookrepositoryImpl(); final BookHandler handler = new BookHandlerImpl(repositorio); return nest(path("/libro"), nest(accept(APPLICATION_JSON), route(GET("/{id}"), handler::getBookFromRepository) .andRoute(method(HttpMethod.GET), handler::getAllBooksFromRepository) ).andRoute(POST("/") .and(contentType(APPLICATION_JSON)), handler::saveBookToRepository)); }
Esto define en unas cuantas lineas de código las siguientes rutas:
- [GET] /libro
- [GET] /libro/{id}
- [POST] /libro
Que mapean las 3 funciones definidas en el handler y las especificaciones que tendría que tener la llamada.
La clase Server además levanta un servidor tomcat incorporado en el puerto 8080 sobre el cual ya es posible iniciar conexiones.
Finalmente en la clase Client tenemos dos métodos que utilizan el WebClient de spring para conectarse al servicio que creamos.
private void createLibro() { final URI uri = URI.create(String.format("http://%s:%d/libro/", HOST, PORT)); final Book libro = new Book(319, "Learning RxJava", "Thomas Nield ", "03.jpg", new BigDecimal("35.95")); final ClientRequest request = ClientRequest.method(HttpMethod.POST, uri) .header("Content-Type", "application/json") .body(BodyInserters.fromObject(libro)).build(); final Mono<ClientResponse> response = exchange.exchange(request); System.out.println(response.block().statusCode()); } private void getAllLibros() { final URI uri = URI.create(String.format("http://%s:%d/libro", HOST, PORT)); final ClientRequest request = ClientRequest.method(HttpMethod.GET, uri).build(); final Flux<Book> productList = exchange.exchange(request) .flatMapMany(response -> response.bodyToFlux(Book.class)); final Mono<List<Book>> productListMono = productList.collectList(); System.out.println(productListMono.block()); }