webflux

Spring Webflux przykładowa aplikacja

Nowa wersja spring framework 5 została wzbogacona o moduł Webflux . Bazuje on na projekcie Reaktor, który umożliwia budowanie aplikacji reaktywnych. Dzisiaj chciałbym zagłębić się bardziej w Webflux’a i opisać pokrótce jak buduje się aplikacje przy użyciu tego właśnie modułu.

Webflux jest rozszerzeniem do budowania aplikacji reaktywnych. Z jego użyciem można to zrobić w bardzo podobny sposób, tak jak robimy to w klasycznej aplikacji spring mvc. Moduł ten pozwala używać tych samych mechanizmów springowych, m.in.: adnotacji @RestController, czy mappingów np. @GetMapping

Stworzymy bardzo prostą aplikację testową, która wystawia usługi restowe, więc z zewnątrz nie będzie różnić się od aplikacji opartej na zwykłym springu.

Zacznijmy od wygenerowania sobie projektu spring boot’owego (przy pomocy spring initializer https://start.spring.io/ lub Inteliji Idea – jeśli ktoś posiada wersję Ultimate). Jedyne moduły jakie musimy wybrać na początek to Reactive Web i Reactive MongoDB.

Poniżej wygenerowane zależności gradlowe:

Po wygenerowaniu projektu należy go zaimportować do IntelliJ (jeśli generowaliśmy na stronie).

Tworzymy kod aplikacji

Teraz możemy przystąpić do napisania pierwszego kontrolera i wystawienia pierwszej usługi. Powiedzmy, że nasz kontroler będzie odpowiedzialny za wystawienie listy książek BookController. Podobnie jak w zwykłym springu, oznaczamy go adnotacją @RestController, a metodę która wystawi nam usługę oznaczmy adnotacją @GetMapping . Jako parametr value podajemy "/books" – do tej pory wszystko jest tak samo, jak w klasycznym springu. Metoda getBooks() wywołuje metodę z repozytorium BookRepository, które jest reaktywnym repozytorium MongoDB.

I teraz pojawiają się różnice: metoda getBooks() zwraca Flux<Book> jeden z dwóch typów reaktywnych dostępnych w spring-webflux (o typach Flux i Mono pisałem więcej w poprzednim artykule). Dla przypomnienia Flux jest publisher’em, który zwraca wieloelementowy strumień, więc zamiast listy książek dostajemy strumień książek. Na razie jeszcze nie do końca widać co to oznacza, ale jeśli dodamy do tego MediaType, w parametrze produces adnotacji @GetMapping i ustawimy go na APPLICATION_STREAM_JSON_VALUE, to już zobaczymy znaczącą różnicę. W tym momencie, dzięki tym dwóm rzeczom, nasza aplikacja powinna zwracać strumień elementów, gdzie każdy z osobna będzie zserializowany do json’a (zamiast zserializowanej listy obiektów w jsonie). Powinno wyglądać to tak, jak na poniższym obrazku:

webflux events
Do testowania używam przeglądarki Firefox

Nasza aplikacja kliencka musi być przystosowana do tego, że otrzymuje strumień json’ów a nie jednego wielkiego json’a.

Żeby lepiej zobrazować i zasymulować opóźnione pojawianie się poszczególnych elementów strumienia, użyłem metody Fluxa .delayElements(Duration.ofSeconds(5)), która opóźnia poszczególne elementy (w tym wypadku o 5 sek.)

W repozytorium MongoDb używamy klasy ReactiveCrudRepository z pakietu org.springframework.data.repository.reactive.

Klasa book musi być oznaczona adnotacją @Document

Strumienie reaktywne

Każdy z elementów strumienia może, zostać zwrócony w różnym czasie. Elementy strumienia będą zwracane dopóki strumień nie zostanie zamknięty. W normalnej komunikacji synchronicznej, serwer zwraca całą odpowiedź w „jednym momencie”. Tutaj dane mogą być po porcjowane.

Prezentuje to poniższy schemat:

reactive vs non-reactive

Możesz testowo usunąć mediaType produces = MediaType.APPLICATION_STREAM_JSON_VALUE z adnotacji @GetMapping. Zobaczysz wtedy diametralną różnicę w sposobie zwracania odpowiedzi przez serwer. Z racji tego, że każdy element strumienia jest opóźniony o 5 sek. serwer będzie czekał, aż zgromadzi wszystkie elementy i strumień zostanie zamknięty. Dopiero wtedy odpowiedź zostanie zwrócona do klienta, więc komunikacja pomiędzy klientem i serwerem będzie synchroniczna. Dodatkowa różnica jest taka, że usługa zwróci wtedy zserializowaną listę json.

Testy integracyjne z WebTestClient

Webfluxa możemy testować przy pomocy klasy WebTestClient. Klienta można skonfigurować automatycznie przez adnotację @AutoConfigureWebTestClient. Ja dodatkowo ustawiłem timeout klienta na 20 sek. ze względu na na to, że używam do opóźnienia elementów metody .delayElements(...). Poza tym nie ma większych różnic w testowaniu webfluxa i tradycyjnej aplikacji springowej.

Usługi wystawiane w sposób funkcyjny

Poza tradycyjnymi adnotacjami, nowy moduł webflux oferuje także wystawianie usług w sposób funkcyjny. W zasadzie, nie różni się to znacząco od tradycyjnego podejścia opartego na adnotacjach. Tylko zamiast adnotacji „rejestrujemy” tak jakby metodę (Handler Function), która będzie wystawiała naszą usługę. I używamy do tego specjalnej funkcji (Route Function).

W spring framework’u w wersji 5 pojawiła się także możliwość tworzenia beanów w sposób funkcyjny.

Poniżej przykład handlera, który będzie obsługiwał naszą usługę.

Gdy mamy już zdefiniowany handler, musimy go podpiąć pod odpowiedni URL. Służy do tego właśnie funkcja route(...) – jest ona klejem, który łączy nasz kod biznesowy w serwerem http. Umieszczona w klasie konfiguracyjnej wystawia naszą usługę na świat.

Funkcyjnie vs Adnotacje

Nie widzę, większej różnicy w obu podejściach. Podejście funkcyjne jest na pewno mniej intuicyjne dla wielu programistów piszących przez lata przy użyciu adnotacji. Na szczęście developerzy springa nie każą nam jeszcze wybierać funkcyjnego podejścia. Moim zdaniem, ciężko będzie przyzwyczaić javowców do podejścia w pełni funkcyjnego. O ile elementy funkcyjne w javie są bardzo przydatne, to w pełni funkcyjne programowanie w javie i używanie elementów funkcyjnych wszędzie tam, gdzie to tylko możliwe raczej się nie przyjmie.

WebClient – reaktywny klient http

Nowy moduł daje nam także nowego reaktywnego klienta http, dzięki któremu możemy wywoływać zewnętrzne usługi restowe.

Klient jest nieblokujący, co sprawia, że musiałem użyć metody .blockLast(), żeby zablokować główny wątek aplikacji, aż zostanie zwrócony ostatni element. W innym wypadku nasz aplikacja zakończyłaby się zanim zdążyłaby otrzymać wynik z wywołania usługi http://localhost:8080/books. Natomiast w normalnej aplikacji reaktywnej zamiast metody .blockLast() użyłbym metody .subscribe().

Podsumowanie

Na pierwszy rzut oka budowanie aplikacji reaktywnych wydaje się bardzo proste. Wystarczy użyć odpowiedniego modułu springa (webflux), zapoznać się z dwoma reaktywnymi typami danych Mono oraz Flux i właściwie to wszystko.

Niestety nie jest tak prosto. Musimy pamiętać przede wszystkim o jednej bardzo istotnej rzeczy. Nie możemy blokować wątków aplikacji, czyli wszystkie czasochłonne operacje muszą być obsłużone w sposób nieblokujący.

Problemy pojawiają się wtedy, kiedy programista nie do końca zdaje sobie sprawę co jest blokujące, a co nie. Używając MongoDb mam pewność, że pobieranie danych z tej bazy jest bezpieczne, ponieważ client MongoDb wspiera taką komunikację. Natomiast praca z sql’owymi bazami danych nie jest możliwa ze względu na blokujące JDBC. Także większość różnego rodzaju bibliotek, które odczytują lub komunikują się z różnymi źródłami danych działa w sposób synchroniczny i zawsze będą blokowały wątki naszej aplikacji. Po naszej stronie stoi obsługa tych wszystkich sytuacji. Są na to wprawdzie różne sposoby, ale zwykle wiążą się one z wieloma problemami.

Przykład na zamieściłem na githubie: springwebflux

Newsletter

Zapisz się na powiadomienia o nowych wpisach

3 komentarze

  1. Artykuł ciekawy i dobrze napisany, ale słowo konstruktywnej krytyki, choć nie w kwestii merytorycznej, a językowej.

    Strasznie mnie kłuje w oczy używanie apostrofu za każdym razem błędnie, a co więcej niespójnie. Apostrof w języku polskim nie służy do oddzielania końcówek fleksyjnych od obcego wyrazu, a wyłącznie do oznaczania, że poprzedzająca apostrof litera nie jest wymawiana.

    Więc tak jak odmienia się np. obraz obrazem (a nie obraz’em), taki i publisher poblisherem (a nie publisher’em), Webflux Webfluxa a. Webfluksa (a nie Webflux’a), boot bootowego (a nie boot’owego), json jsona a. JSON-a (a nie json’a).
    Jeśli wyraz odmieniany jest skrótowcem, do oddzielania końcówki stosuje się dywiz, więc np. HTML-u (a nie HTML’u).

    Co ciekawe w sytuacji gdy zastosowanie apostrofu byłoby poprawne, nie został on użyty. Gradle zostało odmienione jako gradlowej, a tu użycie apostrofu jest zasadne. Końcowe e w gradle jest nieme więc zapis gradle’owej byłby poprawny.

    Jako programiści, skoro dbamy o każdy średnik czy przecinek w kodzie – w języku naturalnym dbać także by wypadało.

    1. Dziękuję za komentarz, szczerze mówiąc nie myślałem, że trzeba to pisać w taki sposób. W przyszłości będę starał się pisać poprawnie, a jak znajdę chwilę to poprawię stare artykuły.

Leave a Reply

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *