Spring 5 introduit le style de programmation réactif pour les API de repos avec webflux . Je suis moi-même assez nouveau dans ce domaine et je me demandais si le fait d'appuyer des appels synchrones vers une base de données dans Flux
ou Mono
est logique au niveau de la préforme? Si oui, est-ce la façon de procéder:
@RestController
public class HomeController {
private MeasurementRepository repository;
public HomeController(MeasurementRepository repository){
this.repository = repository;
}
@GetMapping(value = "/v1/measurements")
public Flux<Measurement> getMeasurements() {
return Flux.fromIterable(repository.findByFromDateGreaterThanEqual(new Date(1486980000L)));
}
}
Existe-t-il quelque chose comme un CrudRepository asynchrone? Je ne l'ai pas trouvé.
Une option consisterait à utiliser des clients SQL alternatifs totalement non bloquants. Quelques exemples: https://github.com/mauricio/postgresql-async ou https://github.com/finagle/roc . Bien sûr, aucun de ces pilotes n'est encore officiellement pris en charge par les fournisseurs de bases de données. De plus, la fonctionnalité est beaucoup moins attrayante que les abstractions basées sur JDBC matures telles que Hibernate ou jOOQ.
L'idée alternative m'est venue du monde Scala. L'idée est de répartir les appels bloquants dans ThreadPool isolé pour ne pas mélanger les appels bloquants et non bloquants ensemble. Cela nous permettra de contrôler le nombre total de threads et permettra au CPU d'effectuer des tâches non bloquantes dans le contexte d'exécution principal avec quelques optimisations potentielles. En supposant que nous avons une implémentation basée sur JDBC telle que Spring Data JPA qui est en effet bloquante, nous pouvons rendre son exécution asynchrone et la distribuer sur le thread dédié bassin.
@RestController
public class HomeController {
private final MeasurementRepository repository;
private final Scheduler scheduler;
public HomeController(MeasurementRepository repository, @Qualifier("jdbcScheduler") Scheduler scheduler) {
this.repository = repository;
this.scheduler = scheduler;
}
@GetMapping(value = "/v1/measurements")
public Flux<Measurement> getMeasurements() {
return Mono.fromCallable(() -> repository.findByFromDateGreaterThanEqual(new Date(1486980000L))).publishOn(scheduler);
}
}
Notre planificateur pour JDBC doit être configuré en utilisant un pool de threads dédié avec un nombre de tailles égal au nombre de connexions.
@Configuration
public class SchedulerConfiguration {
private final Integer connectionPoolSize;
public SchedulerConfiguration(@Value("${spring.datasource.maximum-pool-size}") Integer connectionPoolSize) {
this.connectionPoolSize = connectionPoolSize;
}
@Bean
public Scheduler jdbcScheduler() {
return Schedulers.fromExecutor(Executors.newFixedThreadPool(connectionPoolSize));
}
}
Cependant, cette approche présente des difficultés. Le principal est la gestion des transactions. Dans JDBC, les transactions ne sont possibles que dans une seule connexion Java.sql.Connection. Pour effectuer plusieurs opérations en une seule transaction, ils doivent partager une connexion. Si nous voulons faire des calculs entre eux, nous devons garder la connexion. Ce n'est pas très efficace, car nous gardons un nombre limité de connexions inactives tout en effectuant des calculs entre les deux.
Cette idée d'un wrapper JDBC asynchrone n'est pas nouvelle et est déjà implémentée dans Scala bibliothèque Slick 3. Enfin, JDBC non bloquant peut apparaître sur le Java Comme cela a été annoncé lors de JavaOne en septembre 2016, et il est possible que nous le voyions dans Java 10.
Sur cette base blog vous devez réécrire votre extrait de la manière suivante
@GetMapping(value = "/v1/measurements")
public Flux<Measurement> getMeasurements() {
return Flux.defer(() -> Flux.fromIterable(repository.findByFromDateGreaterThanEqual(new Date(1486980000L))))
.subscribeOn(Schedulers.elastic());
}
Les données de Spring prennent en charge l'interface de référentiel réactif pour Mongo et Cassandra.
Interface réactive de données Spring MongoDb
Spring Data MongoDB fournit un support de référentiel réactif avec les types réactifs Project Reactor et RxJava 1. L'API réactive prend en charge la conversion de type réactif entre les types réactifs.
public interface ReactivePersonRepository extends ReactiveCrudRepository<Person, String> {
Flux<Person> findByLastname(String lastname);
@Query("{ 'firstname': ?0, 'lastname': ?1}")
Mono<Person> findByFirstnameAndLastname(String firstname, String lastname);
// Accept parameter inside a reactive type for deferred execution
Flux<Person> findByLastname(Mono<String> lastname);
Mono<Person> findByFirstnameAndLastname(Mono<String> firstname, String lastname);
@InfiniteStream // Use a tailable cursor
Flux<Person> findWithTailableCursorBy();
}
public interface RxJava1PersonRepository extends RxJava1CrudRepository<Person, String> {
Observable<Person> findByLastname(String lastname);
@Query("{ 'firstname': ?0, 'lastname': ?1}")
Single<Person> findByFirstnameAndLastname(String firstname, String lastname);
// Accept parameter inside a reactive type for deferred execution
Observable<Person> findByLastname(Single<String> lastname);
Single<Person> findByFirstnameAndLastname(Single<String> firstname, String lastname);
@InfiniteStream // Use a tailable cursor
Observable<Person> findWithTailableCursorBy();
}
L'obtention d'un flux ou d'un mono ne signifie pas nécessairement qu'il s'exécutera dans un thread dédié. Au lieu de cela, la plupart des opérateurs continuent de travailler dans le thread sur lequel l'opérateur précédent s'est exécuté. Sauf indication contraire, l'opérateur le plus haut (la source) s'exécute lui-même sur le thread dans lequel l'appel subscribe () a été effectué.
Si vous avez des API de persistance de blocage (JPA, JDBC) ou des API de réseau à utiliser, Spring MVC est au moins le meilleur choix pour les architectures courantes. Il est techniquement possible avec Reactor et RxJava d'effectuer des appels de blocage sur un thread séparé, mais vous ne tireriez pas le meilleur parti d'une pile Web non bloquante.
Alors ... Comment encapsuler un appel synchrone et bloquant?
Utilisez Callable
pour différer l'exécution. Et vous devez utiliser Schedulers.elastic
car il crée un thread dédié pour attendre la ressource bloquante sans bloquer une autre ressource.
exemple:
Mono.fromCallable(() -> blockingRepository.save())
.subscribeOn(Schedulers.elastic());