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

Reactive Application Design with Spring 5, Hamb...

Reactive Application Design with Spring 5, Hamburg Reactive Systems Meetup

The 5th version of the Spring Framework brings a huge step forward in Functional and Reactive Programming support. You don't need ApplicationContext or dozens of annotations to have the simplest REST API up and running. Spring 5 will offer lightweight Web Functions and reactive Web Flux support that can help this transition happen. Those new features make Java and Spring 5 good candidates for building reactive web applications. In addition, we will cover Reactive Systems design principles in general as well as some Resilience Patterns. The talk itself doesn't rely on prior Spring Framework experience so that participants without Spring background could attend the presentation.

More about the event: https://www.meetup.com/Reactive-Systems-Hamburg/events/239818441/

Grygoriy Gonchar

July 11, 2017
Tweet

More Decks by Grygoriy Gonchar

Other Decks in Programming

Transcript

  1. Spring 5 Reactive programming Functional style with Java 8 &

    Kotlin Integration with Java EE 8 APIs Ready for JDK 9
  2. Mono Mono<String> m = Mono .fromCallable(() -> "First") .map(t ->

    t + " and Second") .filter(t -> t.length() % 2 == 0)
  3. Optional semantic Mono<String> m = Mono .fromCallable(() -> "First") .map(t

    -> t + " and Second") .filter(t -> t.length() % 2 == 0) .defaultIfEmpty("Empty");
  4. Nothing happens until you subscribe() Mono .fromCallable(() -> "First") .map(t

    -> t + " and Second") .filter(t -> t.length() % 2 == 0) .defaultIfEmpty("Empty") .subscribe(System.out::println, System.err::println);
  5. Never block() String s = Mono .fromCallable(() -> "First") .map(t

    -> t + " and Second") .filter(t -> t.length() % 2 == 0) // with various operators .defaultIfEmpty("Empty") // and Optional semantic .block();
  6. Parallel and Asyc When Required Flux<Integer> f = Flux.fromIterable(Arrays.asList(1, 2,

    3)) .parallel().runOn(Schedulers.parallel()) .filter(t -> t % 2 == 0) .map(t -> t * 2);
  7. Integrated with Reactive Streams Mono<Integer> m = Flux.from(publisher) // Reactive

    Streams Publisher .parallel().runOn(Schedulers.parallel()) .filter(t -> t % 2 == 0) .map(t -> t * 2) .reduce((t1, t2) -> t1 * t2);
  8. Controlling Back-Pressure Mono<Integer> m = Flux.from(publisher) // Reactive Streams Publisher

    .buffer(300) // communicating demand .parallel().runOn(Schedulers.parallel()) .filter(t -> t % 2 == 0) .map(t -> t * 2) .reduce((t1, t2) -> t1 * t2);
  9. Back-pressure Async message consumption or big datasets bring back-pressure need

    On back-pressure stop demanding (pull-based back-pressure) Otherwise drop messages or signal an error in controlled manner
  10. Application Example Has some business logic Integrates with others Persists

    data Data intensive Uses blocking implementations Resilient Well-tested
  11. Entity public class Person { private String id; private String

    firstname; private String lastname; private String ipAddress; private IpAddressDetails ipDetails; private List<Person> friends; // ... }
  12. Business Logic public Person addPerson(Person person) { IpAddressDetails ipDetails =

    ipService.getDetails(person.getIpAddress()); person.setIpDetails(ipDetails); return repository.save(person); }
  13. Reactive Spring Data public interface ReactiveCrudRepository<T, ID> extends Repository<T, ID>

    { Mono<T> save(T var1); Mono<T> findById(ID var1); Flux<T> findAll(); Mono<Long> count(); ...
  14. Save Person with Reactive Spring Data public interface PersonRepository extends

    ReactiveCrudRepository<Person, String> { // <S> Mono<S> save(S var1); }
  15. Business Logic (Composition of Monadic Types) private Mono<Person> addPerson(Mono<Person> mono)

    { return mono .flatMap(this::addIpDetails) .flatMap(repository::save); }
  16. Business Logic (Nested Monadic Composition) public Mono<Person> addPerson(Mono<Person> mono) {

    return mono .flatMap(this::addIpDetails) .flatMap(repository::save); } private Mono<Person> addIpDetails(Person person) { return ipService.getDetails(person.getIpAddress()) .map(details -> person.copyWithIpDetails(details)); }
  17. Functional Programming and Reactor Reactor & Spring 5 forces to

    use declarative asynchronous functional approach Reactor & Spring 5 doesn’t force purely functional idiomatic (like scalaz, http4s, doobie in Scala) Knowledge of Functional Programming patterns can be useful (such as functional errors, monad transformers, optics etc.)
  18. Local Variable Capture and Mutability public Mono<Person> addPerson(Mono<Person> mono) {

    return mono .flatMap(this::addIpDetails) .flatMap(repository::save); } private Mono<Person> addIpDetails(Person person) { return ipService.getDetails(person.getIpAddress()) .map(details -> person.copyWithIpDetails(details)); }
  19. Immutability public final class Person { private final String id;

    private final String firstname; private final String lastname; private final String ipAddress; private final IpAddressDetails ipDetails; // immutable inside private final javaslang.collection.List<Person> friends; // immutable // init constructor and only getters }
  20. Immutability in Practice Problems with reflection libraries: No default constructor

    for entity No interoperable immutable collections: Javaslang, Guava, JDK9 If done right another challenges come: builders, aggregates, lenses Has better support in Scala, Clojure, Kotlin (and Spring 5 brings Kotlin)
  21. Reactive REST API @PostMapping("/person") public Mono<Person> addPerson(Person p) { return

    service.addPerson(p); } @GetMapping("/person") public Flux<Person> findAll() { return service.findAll(); }
  22. Web Functions return RouterFunctions .route(POST("/person"), request -> { final Mono<Person>

    p = addPerson(request.bodyToMono(Person.class)); return ServerResponse.ok().body(p, Person.class); }) .andRoute(GET("/person"), request -> ServerResponse.ok().body(repository.findAll(), Person.class) );
  23. Data Intensive Processing with Backpressure LocalDateTime dateTime = LocalDateTime.now().minusDays(90); return

    repository.findByUpdatedAtLessThan(dateTime) .flatMap(this::updateIpAddressDetails);
  24. Data Intensive Processing with Backpressure LocalDateTime dateTime = LocalDateTime.now().minusDays(90); return

    repository.findByUpdatedAtLessThan(dateTime) .buffer(300) // communicating demand .flatMap(this::updateIpAddressDetails);
  25. Data Intensive Processing with Backpressure LocalDateTime dateTime = LocalDateTime.now().minusDays(90); return

    repository.findByUpdatedAtLessThan(dateTime) .buffer(300) // communicating demand .parallel(2) // speed-up execution .runOn(Schedulers.newParallel("ipUpdateScheduler", 2)) .flatMap(this::updateIpAddressDetails);
  26. Application Example Has some business logic Integrates with others Persists

    data Data intensive Uses blocking implementations Resilient Well-tested
  27. “Reactive” Spring Data JPA public interface PersonRepository extends CrudRepository<Person, String>

    { // Iterable<T> findAll(); } private Mono<Iterable<Person>> findAll() { return Mono.fromCallable(() -> repository.findAll()); }
  28. “Reactive” Spring Data JPA public interface PersonRepository extends CrudRepository<Person, String>

    { // Iterable<T> findAll(); } private Mono<Iterable<Person>> findAll() { return Mono.fromCallable(() -> repository.findAll()) .publishOn(jdbcScheduler); } @Qualifier("jdbcScheduler") private Scheduler jdbcScheduler;
  29. Dedicated Thread Pool for JDBC @Value("${spring.datasource.maximum-pool-size}") private Integer connectionPoolSize; @Bean

    public Scheduler jdbcScheduler() { return Schedulers.fromExecutor( Executors.newFixedThreadPool(connectionPoolSize)); }
  30. Circuit Breaker Useful to provide your clients fail response fast

    (better UX) Useful when fallback available (such as fallback service provider) Useful to let your dependencies recover
  31. Circuit Breaker Example with resilience4j return Try.of(CircuitBreaker.decorateCheckedSupplier(circuitBreaker, () -> getIpAddressDetails(ip)

    )).getOrElse(() -> getIpAddressDetailsFallback(ip)) .map(this::parse) .onErrorReturn(EMPTY_IP_INFO);
  32. Circuit Breaker Example with resilience4j return Try.of(CircuitBreaker.decorateCheckedSupplier(circuitBreaker, () -> getIpAddressDetails(ip).onErrorResume(t

    -> { circuitBreaker.onError(Duration.ZERO, t); return getIpAddressDetailsFallback(ip); }) )).getOrElse(() -> getIpAddressDetailsFallback(ip)) .map(this::parse) .onErrorReturn(EMPTY_IP_INFO);
  33. Testing Reactive Application You can block() in tests Use testing

    features of your reactive library (like reactor-test) Test your resilience (with or without tools like Netflix's Chaos Monkey) Load test with expected throughput
  34. Conclusions Never block to stay Reactive (flatMap them all) Using

    Blocking IO libraries (such as JDBC) still possible Non-blocking functional programming might give mental overhead Not every application has high enough throughput Blocking can still be resilient, responsive and fast Use Resilience Patterns (such as Bulkheads, Circuit Breaker) in addition