Reactive webservices
Onlangs hadden we een use case waar een API opgebouwd uit reactive webservices een ideale match was. Maar wat zijn "reactive webservices" eigenlijk en waarom zou je ze gebruiken?
Deze blogpost licht alvast een tipje van de sluier.
Reactive webservices zijn een populaire manier geworden om high-performance en schaalbare applicaties te bouwen. Deze services maken gebruik van een asynchrone en non-blocking aanpak om efficiënter te werken met grote hoeveelheden data en requests.
Asynchroon, non-blocking
Mooi, maar wat is nu eigenlijk asynchroon, non-blocking?
Bij een asynchrone en non-blocking aanpak van webservices wordt de verwerking van een verzoek (request) niet geblokkeerd, maar wordt deze in een aparte thread uitgevoerd. Dit betekent dat de server niet wacht tot de verwerking is voltooid voordat deze reageert op het verzoek. In plaats daarvan reageert de server onmiddellijk met een "acknowledgement" (bevestiging) aan de client en laat de verwerking van het verzoek over aan de achtergrondthread.
Dit is in tegenstelling tot de synchrone en blocking aanpak, waarbij de server het verzoek verwerkt en de client pas een reactie terugkrijgt wanneer de verwerking is voltooid. Bij deze aanpak is de server dus geblokkeerd totdat de verwerking is voltooid en kan deze geen andere verzoeken afhandelen terwijl de verwerking nog bezig is.
Tot een tijdje terug, moest je hier zelf heel wat werk voor verrichten om dit te bewerkstelligen door bijvoorbeeld een queue-ing mechanisme of andere concepten uit te werken.
Dit is niet langer nodig dankzij het concept van reactive webservices.
Publish-subscribe
Maar hoe bouw je nu zoiets? Wel door gebruik te maken van een Reactor framework zoals Spring Boot en Webflux. Deze biedt een set van tools en bibliotheken om een reactieve applicatie te bouwen. Het Reactor framework maakt gebruik van de Publisher-Subscriber patroon, waarbij publishers events of data sturen en subscribers de gegevens verwerken en erop reageren.
public class ReactiveController {
@GetMapping("/reactive")
public Mono<String> getReactiveData() {
return Mono.just("Hello Reactive World!");
}
Mono's en fluxen
In het bovenstaande voorbeeld hebben we een eenvoudige REST-controller met een enkel eindpunt dat een Mono retourneert.
We introduceren hier een "Mono". Een belangrijk onderdeel van het werken met Project Reactor is het begrijpen van het verschil tussen Mono en Flux. Mono en Flux zijn beide implementaties van de Publisher interface die door Reactor wordt aangeboden. Mono is bedoeld om nul of één item uit te zenden, terwijl Flux bedoeld is om nul of meer items uit te zenden.
Beide concepten kunnen dus via een subscribe geconsumeerd worden. Consumeren doen we met behulp van de subscribe-methode. Deze methode accepteert een lambda-uitdrukking die wordt gebruikt om de resultaten te verwerken
getMonoData().subscribe(result -> { System.out.println(result); });
// Flux consumeren
getFluxData().subscribe(result -> {System.out.println(result);});
Merk op dat de Mono-stroom slechts één resultaat retourneert, terwijl de Flux-stroom meerdere resultaten retourneert, dus de lambda-uitdrukking zal meerdere keren worden uitgevoerd.
Merk op dat naast het "subscriben" er nog andere manieren zijn om Mono's en Fluxen te consumeren en te manipuleren, zoals bijvoorbeeld het filteren, transformeren en combineren van de data.
Hierbij enkele kleine voorbeelden, waarbij een flux wordt gefilterd, getransformeerd en gecombineerd.
Flux<Integer> numbers = Flux.range(1, 10);
Flux<Integer> evenNumbers = numbers.filter(n -> n % 2 == 0);
evenNumbers.subscribe(n -> System.out.println(n));
// prints 2, 4, 6, 8, 10
//Mappen van een flux
Flux<String> numberStrings = numbers.map(n -> String.format("Number %d", n));
numberStrings.subscribe(s -> System.out.println(s));
// prints "Number 1", "Number 2", "...
//combineren van Monos
Mono<String> firstName = Mono.just("John");
Mono<String> lastName = Mono.just("Doe");
Mono<String> fullName = Mono.zip(firstName, lastName, (first, last) -> String.format("%s %s", first, last));
fullName.subscribe(name -> System.out.println(name));
// prints "John Doe"
Voor- en nadelen:
Voordelen:
- Efficiëntie: Project Reactor is ontworpen om efficiënt om te gaan met grote hoeveelheden data en requests, wat het bijzonder geschikt maakt voor het bouwen van reactieve webservices.
- Schaalbaarheid: De asynchrone en non-blocking aanpak maakt het eenvoudiger om te schalen naar een groot aantal gelijktijdige verzoeken.
- Flexibiliteit: Spring Boot Reactor biedt een set van tools en bibliotheken die kunnen worden aangepast en geconfigureerd om aan de specifieke vereisten van een project te voldoen.
Nadelen:
- Leercurve: Project Reactor heeft een redelijk steile leercurve en vereist kennis van asynchrone en non-blocking programmering.
- Complexiteit: Het bouwen van reactieve services kan complex zijn en vereist meer code dan traditionele services.
- Minder ondersteuning: Aangezien reactieve services nog relatief nieuw zijn, is er minder ondersteuning beschikbaar in vergelijking met traditionele services.
Al met al biedt Project Reactor een krachtige en flexibele manier om reactieve services te bouwen. Terwijl de leercurve en complexiteit uitdagend kunnen zijn, biedt het ook voordelen op het gebied van efficiëntie en schaalbaarheid. Het is belangrijk om de specifieke vereisten van een project te overwegen bij het beslissen of Reactor geschikt is voor de toepassing.
References
- Photo by Pixabay: Pexels.com
- Project Reactor
- Spring Webflux