Vous avez équipé votre maison d'un capteur de température, et vous souhaitez faire un programme qui récupère ses valeurs. Pour le moment nous simulerons le capteur, en émettant simplement un flux.
Créer un Mono ou un Flux à partir d'une valeur : https://projectreactor.io/docs/core/release/reference/#_simple_ways_to_create_a_flux_or_mono_and_subscribe_to_it
Transformer une valeur : https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Mono.html#map-java.util.function.Function-
Lancez les tests dans TemperatureServiceTest
.
-
Implémentez la méthode
getLastTemperatureAsFloat
pour qu'elle retourne la température dans un Mono Le testgetLastTemperatureAsFloat_should_emit_LAST_TEMPERATURE
devrait réussir. -
Implémentez la méthode
getLastTemperatureData
pour qu'elle souscrive au Publisher renvoyé pargetLastTemperatureAsFloat
et transforme le résultat émis en Temperature. Le testgetLastTemperatureData_should_emit_one_value
devrait réussir. -
Complétez la méthode
getLastTemperatureData
pour qu'elle transforme la température en farenheit. Le testgetLastTemperatureData_should_emit_LAST_TEMPERATURE_in_farenheit()
devrait réussir.
Vous aurez besoin de transformer votre température avec la méthode
toFahrenheit
- Complétez la méthode
getLastTemperatureDatas
pour qu'elle retourne un Flux de TemperatureData Le testgetLastTemperatureDatas_should_emit_all_temperatures
devrait réussir.
Les valeurs des températures sont dans LAST_TEMPERATURES
- Complétez la méthode
generateTemperatureData
pour qu'elle retourne un flux infini deTemperatureData
. Cette méthode doit émettre un élément toutes les 100ms.
Vous aurez besoin de l'opérateur Flux.interval() pour générer le Flux Vous pouvez utiliser la méthode
generateFloat()
Le but de cet exercice est de migrer notre controller en annotation vers une RouterFunction.
Commencez par lancer les tests d'intégration dans TemperatureHandlerTest.
- Refactorisez le code pour utiliser les router functions.
1.1. Créez une classe WebConfig pour définir vos RouterFunction
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
}
1.2. Créez des beans pour vos RouterFunction
Vous devez migrer 3 routes :
- @GetMapping(path = "/temperature/last")
- @GetMapping(path = "/temperature/lasts")
- @GetMapping(path = "/temperature-event",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
Commencez par les tester pour vérifier que le projet fonctionne, et lancez les tests d'intégration dans TemperatureHandlerTest
@Bean
public RouterFunction<?> router() {
return route()
.path("/...", builder ->
builder.nest(//
accept(MediaType.ALL), builder2 -> //
builder2//
.GET("/r1", request -> temperatureHandler.getLastTemperatureData())
.GET("/r2", request -> temperatureHandler...())
.build()
))
.before(this::logRequest)
.build();
}
@Bean
public RouterFunction<?> routerSSE() {
return route()
.GET("/r3", request -> temperatureHandler.getTemperatureData())
.build();
}
- Transformez la classe TemperatureHandler pour que vos méthodes retournent des
Mono<ServerResponse>
.
Voici un exemple basique :
public Mono<ServerResponse> getLastTemperatureData() {
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(temperatureService.getLastTemperatureData(), Temperature.class);
}
Cet exemple fonctionne, mais ne gère pas le cas où notre méthode ne renvoie aucune valeur. Voici un autre exemple :
public Mono<ServerResponse> demo2() {
return temperatureService.getLastTemperatureData()
.flatMap(res -> ok()
.contentType(MediaType.APPLICATION_JSON)
.body(fromObject(res)))
.switchIfEmpty(notFound().build());
}
- Vous pouvez également gérer le cas où votre publisher vous renvoie un signal d'erreur :
public Mono<ServerResponse> demoWithError() {
return temperatureService.getLastTemperatureData()
.flatMap(res -> ok()
.contentType(MediaType.APPLICATION_JSON)
.body(fromObject(res)))
.doOnError(res->ServerResponse.status(500).build())
.switchIfEmpty(notFound().build());
}
- Le test d'intégration doit toujours fonctionner, néanmoins vous allez devoir ajouter votre classe de configuration dans la configuration de votre test :
@RunWith(SpringRunner.class)
@WebFluxTest(TemperatureHandler.class)
@ContextConfiguration(classes = {
TemperatureHandler.class,
TemperatureService.class,
WebConfig.class
})
public class TemperatureHandlerTest {
}
- Améliorez vos routerFunction
Ajoutez la méthode .before()
dans vos router functions pour logguer toutes les requêtes entrantes.
Dans cet exercice nous allons appeler les API précédemment créées. Pour cela nous allons utiliser WebClient ( https://www.baeldung.com/spring-5-webclient ), que nous allons initialiser puis utiliser.
Le WebClient est initialisé dans notre constructeur.
Pour cet exercice les tests se feront grâce à la classe. Vous devez préalablement démarrer votre module API.
- Mono
Implémentez la méthode getLastData()
pour appeler votre route
/temperature/last
qui renvoie un Mono
- Flux
Implémentez la méthode getLastDatas()
pour appeler votre route
/temperature/lasts
qui renvoie un Flux
- ServerSentEvent
Implémentez la méthode getTemperatureEvent()
pour appeler votre route /temperature-event
qui renvoie des ServerSentEvent
. Pour ce cas, vous devrez transformer votre Flux de cette manière :
.bodyToFlux(typeReference);
Nous souhaitons souscrire aux événements provenants du module API (http://localhost:8081/temperature-event) et effectuer plusieurs opérations :
- aggrégation de plusieurs températures en une seule
- calculer la temperature moyenne de cette liste
- persister la température moyenne en base de données
- Le module Data dispose des dépendances suivantes
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<version>2.2.0</version>
</dependency>
-
Ajoutez une interface TemperatureRepository utilisez la classe document Temperature à votre disposition.
-
Dans la classe DataService modifiez la méthode
temperatureEventFlux()
afin qu'elle effectue les opérations suivantes :-
Souscrire aux événements en utilisant la méthode
webClient.getTemperatureEvent()
-
Un mapping de type de Flux<ServerSentEvent> vers Temperature Vous pouvez utiliser la classe JacksonConverter
-
Un calcul de moyenne de température pour 10 températures Vous pouvez utiliser la méthode
computeAverage(List<Float> floatList)
-
La création d'un document mongo Vous pouvez utiliser la méthode
buildDocument(Float aFloat)
-
La persistence en base de données
-
-
Terminez d'implémenter la méthode DataController#getAll() afin qu'elle retourne tous les documents de la collection temperature.
-
Testez dans un navigateur que votre implémentation fonctionne via la route http://localhost:8082/all
Nous souhaitons que notre module front affiche en temps réel les nouvelles moyennes de températures calculées et persistées. Pour ce faire vous allez mettre en oeuvre l'API Processor.
-
Ecrivez une classe TemperatureProcessor qui exposera des beans de DirectProcessor et de FluxSink.
On utilisera l'implémentation DirectProcessor, l'API expose deux méthodes permettant sa mise en oeuvre :
DirectProcessor.create()
: Retourne une instance de DirectProcessor sur la quelle il est possible d'effectuer une souscriptionDirectProcessor#sink()
: Retourne une instance de FluxSink qui permet de pousser de la donnée
-
Modifier la classe MongoListener afin que cette dernière notifie le Processor de chaque document persisté
-
Completez la méthode DataController#getTemperatureEvent() afin de souscrire au Processor
- Vous pouvez chaîner l'opérateur
log()
afin d'afficher des logs sur les connexions en cours
- Vous pouvez chaîner l'opérateur
-
Exécutez le front afin de vérifier le bon fonctionnement