Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Java Reactivo - Construyendo un back-end funcional con Spring WebFlux

Java Reactivo - Construyendo un back-end funcional con Spring WebFlux

Quito Lambda

January 29, 2020
Tweet

More Decks by Quito Lambda

Other Decks in Programming

Transcript

  1. ¡Presenta tus ideas en Quito Lambda! Si tienes algo interesante

    que presentar, ¡ven a presentar con nosotros! Programación Funcional, DevOps, Cloud Computing, IaaS, Desarrollo Web, React, WebGL, ReasonML y cualquier cosa interesante [email protected]
  2. Temas • Revisión - Java funcional • ReactoreCore - Mono<T>

    y Flux<T> • Rutas funcionales con Spring WebFlux • Demo • Conexión reactiva a una base de datos relacional - PostgreSQL • ¿Cómo probamos código functional y reactivo? (tests)
  3. Revisión - Java funcional (https://youtu.be/h-WO7czupsE) • Inmutabilidad ◦ @Builder con

    “Project Lombok” • Expresiones Lambda y @FunctionalInterface ◦ myList.filter(item -> item % 2 == 0); ◦ myStream.toArray(String::new); • Manejo de valores nulos - Optional<T> • Flujo de datos - Stream<T> • Interfaces funcionales Lambda incorporadas ◦ Predicate<T>, Consumer<T>, Supplier<T>, Function<T, R>
  4. Mono<T> y Flux<T> Es una librería Reactiva de 4ta generación,

    que nos permite construir aplicaciones asíncronas 100% no-bloqueantes. Interactúa directamente con el API funcional de Java. Reactor ofrece 2 APIs reactivas componibles: Mono [0|1] y Flux [N] ¿Qué quiere decir Reactivo o “Reactive Streams”? - Reactive Streams es una iniciativa que proporciona un estándar para el procesamiento de flujos asíncronos no-bloqueantes con contrapresión (backpressure).
  5. Mono<T> y Flux<T> Son 2 tipos que nos permiten manejar

    el flujo de data Ambos implemetan Publisher<T> de ReactCore Flux lleva de 0 a N datos. Mono lleva 0 o 1 Flux<String> just = Flux.just("1", "2", "3"); Mono<String> just = Mono.just("foo"); Flux<String> empty = Flux.empty(); Mono<String> empty = Mono.empty(); ¿Por qué no solo Flux? - Cardinalidad y contra presión (backpressure)
  6. Mono<T> y Flux<T> Nada sucede hasta que no nos suscribimos

    al Publisher<T> Flux<Integer> data = Flux.just(1, 2, 3, 4); .map(n -> n * 2); System.out.println(elements); // [] data.subscribe(elements::add); System.out.println(elements); // [2, 4, 6, 8] Manejamos código asíncrono y podemos elegir en qué hilo ejecutarlo. Mono.create(sink -> { callWebService("http://foo", response -> { sink.success(response); }); }) .subscribeOn(Schedulers.newElastic("myThread")) .map(...) .subscribe(...);
  7. Mono<T> y Flux<T> Con el API podemos: • Transformar el

    contenedor de data: Mono<T> Flux<T> • Crear o inicializar: .empty(...), .just(...), .create(...), .fromSupplier(...) • Transformar los datos: .map(...), .filter(...), .flatMap(...), .zip(...) • Controlar errores y vacíos: .doOnError(...), .switchIfEmpty(...), .or(...) • Concatenar con la señal de otros Publisher<T>: .and(...), .then(...) • Convertir el tipo de los datos: .cast(...) • Generar logs y metricas: .log(...), .metrics() • Suscripción e hilos: .subscribe(...), .subscribeOn(...), .delaySubscription(...) • Y mas...
  8. Rutas funcionales con Spring WebFlux Mantiene compatibilidad con Spring MVC

    - Podemos utilizar @RestController para crear rutas. Que tan funcional/reactivo sería esto? WebFlux provee RouterFunction<T extends ServerResponse>: - Es libre de efectos secundarios. Retornamos la respuesta. - Es funcional, podemos concatenar rutas, aplicar filtros, etc. sin código imperativo. - Es reactivo, se ejecutan de manera asíncrona. Permite alta concurrencia sin bloquear Podemos extender la configuración implementando WebFluxConfigurer: - Conversión de mensajes: configureHttpMessageCodecs(ServerCodecConfigurer configurer) - Configuración de CORS: addCorsMappings(CorsRegistry registry) - Resolución de vistas: configureViewResolvers(ViewResolverRegistry registry)
  9. Rutas funcionales con Spring WebFlux @Configuration public class MainRouter {

    @Bean public RouterFunction<ServerResponse> router() { return route( GET("/greeting"), request -> ok().bodyValue("Hello world!") ); } }
  10. Conexión reactiva a Base de Datos SQL • WebFlux fue

    inicialmente pensado para trabajar con bases de datos no relacionales ◦ Existen implementaciones de repositorios de datos reactivos para Mongo, Cassandra, Redis, Couchbase. • ¿Qué hay de JDBC y JPA? Son reactivas? • El proyecto Spring Data R2DBC (Reactive Relational Database Connectivity): ◦ Implementaciones reactivas de bases de datos relaciones: PostgreSQL, H2, MSSQL ◦ Se basan en la idea del popular Hibernate ◦ Con todos los beneficios de Spring Boot y JPA ◦ Spring Data R2DBC no es un ORM, está orientado a ser conceptualmente simple
  11. Conexión reactiva a Base de Datos SQL La solución que

    Spring Boot nos trae: 1. Parámetros de conexión a nuestra base de datos (application.yml) 2. Entidad(es) que aten nuestras tablas con objetos Java 3. Repositorio(s) que abstraen la interacción con la base de datos Opcional: • Control de versión de base de datos: Spring DDLs, Flyway, Liquibase
  12. 1. Parámetros de conexión Usamos application.yml o application.properties: spring: r2dbc:

    username: quitolambda password: 1234 url: r2dbc:postgres://localhost:5432/quito-lambda-demo --- url: r2dbc:driver:proxy://username:password@host:port/db-name?queryparams=...
  13. 2. Entidad @AllArgsConstructor @Builder(toBuilder = true) @Getter public class Person

    { @Id private Long id; private String name; private String lastname; private Date birthdate; }
  14. 3. Repositorio public interface PersonRepository extends ReactiveCrudRepository<Person, Long> { @Query("SELECT

    * FROM person WHERE last_name = :lastname") public Flux<Person> findByLastname(String lastname); }
  15. ¿Cómo lo usamos? @Autowired private PersonRepository personRepo; personRepo.saveAll( Arrays.asList( new

    Person("Paul", "McCartney", new Date(...)), new Person("Bob", "Dylan", new Date(...)), new Person("Keith", "Richards", new Date(...)) ) ) .subscribe(); Mono<Long> total = personRepo.count(); personRepo.findAll() .doOnNext(person -> { log.info(person.toString()); }) .subscribe(); Mono<Person> paul = personRepo.findById(1L); Mono<Person> keith = personRepo.findByLastname("Richards");
  16. Configuración programática @Bean public ConnectionFactory connectionFactory() { final ConnectionFactoryOptions options

    = ConnectionFactoryOptions.builder() .option(ConnectionFactoryOptions.USER, "quitolambda") .option(ConnectionFactoryOptions.PASSWORD, "1234") .option(ConnectionFactoryOptions.DRIVER, "postgres") .option(ConnectionFactoryOptions.HOST, "localhost") .option(ConnectionFactoryOptions.PORT, 5432) .option(ConnectionFactoryOptions.DATABASE, "quito-lambda-demo") .build(); return ConnectionFactories.get(options); // return ConnectionFactories.get("r2dbc:postgres://quitolambda:1234@localhost:5432/quito-lambda-demo"); }
  17. Cómo probamos código functional y reactivo? Cuál es el problema?

    Código es no bloqueante, asíncrono y funcional. No podemos probarlo de forma imperativa. Bloquear en hilos de ReactorCore no es permitido Probar el flujo de datos (Mono y Flux)
  18. ¿Cómo probamos código functional y reactivo? Usamos la solución de

    Reactor StepVerifier API que nos ayuda a verificar de manera declarativa y paso a paso las secuencias asíncronas Ambiente controlado para manejar un reloj virtual (evadir pruebas largas) Verificar que completó o finalizó con un error esperado Cancelar la subscripción para finalizar (rollback)
  19. ¿Cómo probamos código functional y reactivo? Podemos probar las rutas

    funcional? Pruebas de integración Spring trae la abstracción WebTestClient API permite usar el contexto de la aplicación en las pruebas. El contexto contiene las rutas funcionales
  20. StepVerifier @SpringBootTest class WebfluxApplicationTests { @Autowired private CustomerRepository customerRepo; @Test

    void customerTest() { final Customer customer = new Customer("Bob", "Dylan"); final Mono<Customer> found = customerRepo.save(customer) .flatMap(saved -> customerRepo.findById(saved.getId())); StepVerifier.create(found) .assertNext(bob -> { assertThat(bob.getId()).isGreaterThan(0L); }) .verifyComplete(); } }
  21. StepVerifier @SpringBootTest class WebfluxApplicationTests { @Autowired private CustomerRepository customerRepo; @Test

    void customerTest() { customerRepo.save(new Customer("Bob", "Dylan")) .map(Customer::getId) .flatMap(customerRepo::findById) .as(StepVerifier::create) .assertNext(customer -> { assertThat(customer.getId()).isGreaterThan(0); }) .verifyComplete(); } }
  22. WebTestClient @SpringBootTest class WebfluxApplicationTests { @Autowired private ApplicationContext appContext; @Test

    void mainRouterTest() { WebTestClient.bindToApplicationContext(appContext) .build() .get() .uri("/greeting") .exchange() .expectStatus() .isOk() .expectBody(String.class) .isEqualTo("Hello world!"); } }