Transkrypcja
Wstęp do nowego tematu
Cześć, Wojtek!
Cześć, Michał!
Zaczynamy chyba nowy odcinek, tylko nie wiem, jakie będą reakcje.
Jakie będą reakcje na wieść o porzuceniu tematów gier?
W ogóle, na wieść o nowej, o nowej treści.
Nasze reakcje na pewno będą smutne.
Nie no, tam coś będziemy wplatać. No, wiadomo, że programowanie reaktywne to i w grach występuje, nie?
A bo taki jest temat?
Tak.No to dobra…
Mieliśmy o reaktywności opowiadać. A to wychodzi na to, że te gry to są takie bardzo reaktywne.
Reaktywne. No, może niekoniecznie tam mają wszystkie aspekty jakoś z manifestu wyryte. No właśnie, ale co to jest reaktywność? To ja mam mówić? A nie wiem, no mogę ja też. Nie musisz pytać.
Dobraa, co to jest ta reaktywność? A więc, zgodnie z Wikipedią…
Czym jest reaktywność w kontekście architektury oprogramowania?
Nie, no właśnie tutaj chyba poruszymy sobie tą reaktywność w kontekście takim bardziej podejścia architektury i też zahaczymy pewnie też o trochę takie detale implementacyjne. No bo tu możemy to na różnych poziomach tą naszą reaktywność. Samo oprogramowanie reaktywne to wiadomo, są pewne mechaniki, frameworki, jakieś rozwiązania, żeby te nasze reaktywność, czymkolwiek ona by nie było, jakoś zamodelować w tym kodzie. No bo tego, żeby to zrobić, trzeba użyć pewnych technik, troszkę innych niż takie zwykłe linijka po linijce. Trzeba być mechanikiem. I tutaj ta reaktywność i ten mindset to jest dość ważne, bo no bo ten mindset jest troszkę inny niż przy takim zwykłym, zwykłym, można powiedzieć, podejściu. Natomiast chyba zaczniemy od tego, czym jest w ogóle reaktywność w takim rozumieniu właśnie no, architektury i podejścia.
A może, jak zanim byśmy zaczęli od właśnie takich technicznych rzeczy, to w ogóle językowo moglibyśmy też się temu przyjrzeć, nie? Czyli słowotwórstwo.
Słowotwórstwo. Ktoś to wymyślił też musi być reaktywny. No i jak to żeś wytłumaczył? No, najprościej chyba tak, że musi być gotowy na jakąś reakcję. Ta reakcja, w sensie, musi być, musi wynikać z tej gotowości, którą mamy i to jest chyba w odróżnieniu od tego musi nasłuchiwać.
Nasłuchiwać, tak. I no i być w tej gotowości. I jak to coś, na co nasłuchujemy, się już wreszcie zdarza, to wtedy reagujemy. I wtedy trzeba zareagować. No, w taki sposób bardzo ogarnięty i tutaj właśnie przychodzą z pomocą te architektury, wzorce, wszystkie te, nawet te właśnie te pryncypia, które tam są w manifeście wyszczególnione.
Tak.
To one przychodzą tutaj i dają nam te możliwości do zareagowania.
Tak, bo jak każdy szanujący się ruch czy społeczność, sekta,reaktywność ma też swój Manifest, tak. Tak jak Agile Manifesto jest też Reactive Manifesto, więc można sobie tam podejrzeć taki krótki, no, krótki manifest, można powiedzieć, to opisujące to, co twórcy, jakby, mają na myśli, mówiąc właśnie o reaktywności i jakby też podają przyczyny i powody, dlaczego w ogóle te reaktywność jest według nich potrzebna. No i jednym z tych powodów i jakby, dlaczego w ogóle chcemy w te reaktywności iść według twórców, jest to właśnie, że XX-wieczne sposoby rozwiązywania pewnych implementacji systemów no, nie specjalnie nadają się do XXI-wiecznych aplikacji i zastosowań (i wymagań) tychże aplikacji.
A co dopiero od XIX-wieczne?
A są dopiero XIX- wieczne. To już to już w ogóle.
Dlatego tylko dobrze w serii „Anno” wygląda.
Tak, tak i tak. Tak. 1800, 1600. Czyli nawet XVII-wiecznych.
A zróbmy jakiś Steampunk Manifesto…
Kluczowe cechy aplikacji reaktywnych
W każdym razie, jakby, już mówiliśmy o tym też w kilku poprzednich odcinkach, że no, aplikacje stają się coraz bardziej skomplikowane, mają coraz więcej komponentów, jakiś zależnych, nasłuchują nas coraz więcej, większej ilości różnych innych systemów. Mamy, mamy integralności zdarzeń, w ogólności zdarzeń, tak. Bo to to głównie jakby do tego możemy to chyba sprowadzić, rzeczywiście jakieś zdarzenia, czy to będzie zdarzenie z aplikacji mobilnej, czy z jakiejś, z jakiegoś API, to już jakby na tym poziomie nas może trochę mnie interesuje. No i rzeczywiście twórcy tej, w tym manifeście, twierdzą, że może trzeba pewnych nowych sposobów właśnie wytwarzania i ich podejścia do oprogramowania.
A jeszcze, jeszcze można, przepraszam, wtrącić, że tych nowych sposobów, ale w taki bardziej ustandaryzowany sposób, w sensie zebranych w bardziej standardowe narzędzia.
Tak, tak. Czy też koncepty po prostu takie, które były. No bo wiadomo, że skoro to zagadnienie, nie chciałem tego problemu nazywać, ten zestaw wymagań pojawia się coraz częściej w szerokim w ogóle spektrum domen, praktycznie wszędzie to występuje, to dobrze jest mieć narzędzia, które pozwolą w każdym z tych obszarów poradzić sobie w jakiś podobny sposób, a nie żebyśmy musieli odkrywać koło na nowo i jakoś to inaczej implementować.
Dokładnie, no też trzeba sobie powiedzieć, że to nie są jakieś nowe zupełnie rozwiązania, bo jakaś tam reaktywność, już nie mówiąc o innych rozwiązaniach, które tu będziemy omawiać, one już istnieją od pewnego czasu. Kilkudziesięciu lat. Można powiedzieć, ubrane w trochę inne słowa. No i twórcy właśnie podałem kilka takich kluczowych czynników, które usprawnią czy właśnie ułatwią tworzenie takich współczesnych, nowoczesnych aplikacji, czyli generalnie responsywność. I rozumiana właśnie responsywność, czy to tak zwyczajnie przez responsywność aplikacji czy interfejsu użytkownika, że aplikacja rzeczywiście coś robi, informuje użytkownika o tym, że coś zrobiła, że coś się dzieje, coś się przetwarza, a nie że po prostu widzimy biały ekran, czy totalnie jakąś schizowaną, zawyżoną.
Jak widzimy kręciołka jakąś, to jeszcze dobrze w miarę.
Jak widzimy kręciołka, tylko dobrze, żeby ta kręciołka też się skończyła i się kręciła i się skończyła albo zakończyła się błędem, a nie że kręci się w nieskończoność i użytkownik cały czas wciska F5. I jeszcze pogarsza.
I jeszcze pogarsza. Wiadomo. Więc to chodzi o taką responsywność. A to chodzi też o responsywność w sytuacjach również awaryjnych, czyli tak zwane aplikacje muszą być resilient, czyli musi być odporna na awarie. To nie znaczy, że awarie nie będą się zdarzać, bo wiadomo, że nie da się wszystkim awariom zapobiec, ale w razie jeśli taka awaria się wydarzy w aplikacji, musimy zagwarantować, że albo w miarę szybko, albo w niezauważalny sposób ona to zakomunikuje użytkownikowi, albo potrafi się odtworzyć, zrestartować.
To, panie Tomku, to już umie od lat, HTTP 500. No to jest taki bardzo prosty sposób responsywności. Lepszy takie coś, niż zupełnie biały ekran.
Teoretycznie nawet takie wyrzucenie z taką trasą, jak to czasami bywało w niektórych aplikacjach, że dostajemy na front, który jest z wyjątkiem. To też jest pewien sposób responsywności. Zgłoszenia, tak. I to się wysyła. Przynajmniej mamy jakiś komunikat, że wiemy, że coś się dzieje, bo zupełnie gorzej jest, kiedy nic się nie dzieje, bo wtedy to już nic nie wiemy, czy to się w ogóle zakończyło, czy nie.
Tak jest. W każdym bądź razie, to to odporność tych aplikacji i zdolność właśnie do samonaprawiania się no to sprawia, że jednak jej dostępność, jej jakby poczucie użytkownika, że ta aplikacja rzeczywiście jest inna, jest, jest trochę lepsza. Za tym też idzie, jakby, w drugą stronę, elastyczność w rozumieniu skalowalności tej aplikacji. Bo co innego, kiedy aplikacja nam się zupełnie wywaliła, bo coś się stało, ale są też sytuacje, kiedy aplikacja może nam do tego stopnia zwolnić, bo mamy tylu użytkowników, że też nie odpowiada. Więc w drugą stronę, jeśli mamy chwilowy pik, jeśli mamy jakiś większy, większy natężenie ruchu, więcej requestów, więcej czegokolwiek, musimy mieć też pewne mechanizmy, żeby, żeby ten nasz, aplikacja była w stanie odpowiednio obsłużyć ten ruch i odpowiednio też zakomunikować użytkownikowi, że na przykład w tym czasie jeszcze jego request jest przetwarzany, że coś tam się dzieje, że Poczekaj, drogi użytkowniku, bo to zajmie trochę więcej.
Tak. I to już są takie solidne nawiązania do w ogóle takiego paradygmatu message-driven, który już sam w sobie wymusza zmianę koncepcji, znaczy zmianę sposobu, w jaki patrzymy na taki system, że jeżeli my zainicjalizujemy jakąś akcję tam i spodziewamy się, że jest to jakiś, no to z tych grubszych proces biznesowy, to niekoniecznie musi się zakończyć w tym momencie, kiedy do nas przychodzi response tego requestu, nie? Czyli, że jak najbardziej legalne jest po prostu wysłanie requestu do takiej aplikacji i otrzymanie odpowiedzi: „OK, zaczęliśmy”. A to, że tam się kółko kręci gdzieś tam, przy czym ona może być na razie na przykład niewidoczna, nie? Bo dopiero wchodzimy. Powiedzmy, że generujemy jakiś raport. Nie? No to zlecamy, określiliśmy parametry tego raportu, odpaliliśmy tą generację i tam teraz wielkie widoki się mielą na tej maszynie. Trochę to jest takie przeciw temu naszemu, co, co tutaj zgłaszamy, nie? Ale załóżmy, że jest tam jakiś długotrwały proces, który sprawdzimy sobie dopiero w innym miejscu, nie? I to już podejście message-driven nas zaczęło do tego przyzwyczajać. Że nie dało się tego zrobić, ale także i inne podejście. Niekoniecznie takie stricte message-driven, bo zwykły nieśmiertelny przykład sklepu internetowego to jest też przykład właśnie takiej responsywności i takiego resilience, gdzie rozpoczynamy cały ten nasz ten flow kupowania i wiemy, że on musi przejść przez bramkę płatności i jeszcze tam przez jakieś stany magazynowe i cuda wianki, na końcu jakąś fakturę. I na każdy… I to wszystko są jakieś osobne w ogóle klocki. Więc na pewno jakieś komunikaty latają pomiędzy nimi, ale ten proces, który rozpoczynamy w ogóle, wkładając cokolwiek do koszyka, on się dopiero kończy daleko później, jak już ta nasza płatność wręcz zaksięguje, bo teraz to online się dzieje, jeżeli korzystamy z jakiejś tam bramek płatności. No i dopiero ten ostateczny wynik to właściwie w mailu przeczytamy, że, że spoko. Nie? Że się udało, że przyjęte.
Bo na przykład tu nawet może nam zgasnąć interfejs, ale nie szkodzi, bo ten proces się tam dzieje i ona z poinformuje. Jeszcze tak naprawdę dużo kroków, które nie widać.
Nie widać. Tak. Jakieś właśnie wysyłki z magazynu. Po wysyłce może pójdzie jakiś komunikat, żeby uzupełnić zapasy magazynowe, bo może się okazać, że to była ostateczna sztuka. Więc.
Dokładnie. A ta ostatnia sztuka to była i teraz ta nowa jedzie do nas z Chin.
I tam widać po kolei, przez które portale…
Dokładnie. Właśnie message-driven i ogólnie właśnie wysyłanie komunikatów jest według też autorów całego manifestu tak trochę podstawą tych trzech pierwszych cech, czyli właśnie responsywności, elastyczności, bycia resilient. No bo właśnie responsywność to jest odpowiedzia, odpowiedź na te komunikaty. Nie? No, to jest tak pochodna tego, że jeśli system jest responsywny, znaczy, że jednak komunikuje się w taki dość granularny, fajny sposób, a nie tylko, nie tylko, nie tylko za pomocą jakiegoś jednego wielkiego statusu OK i tyle.
Adam właściwie, co okej? Tak naprawdę wszystko, no i to wszystko.
No i twórcy sobie tak to sobie wymyślili i takie są według nich zasady gdzieś tam tworzenia tych aplikacji. Tutaj nie mówimy jakby o też o architekturze od razu tej aplikacji, bo teoretycznie można sobie pomyśleć, że to od razu narzuca mikroserwisy, ale to nie jest tak od razu jednoznaczne.
Porządny monolit to ogarniemy.
Też można to. Właśnie o to chodzi, że to tak naprawdę nie narzuca od razu stricte jakiejś architektury wdrożeniowej czy ogólnie budowania właśnie już aplikacji bardziej implementacyjnie. To bardziej mówimy o takich cechach, które powinno zapewnić nam to oprogramowanie. Jeśli mielibyśmy zejść niżej z kolei i powiedzieć właśnie, jak to oprogramowanie miałoby to robić, no to zwykle tutaj już pojawiają się takie słowa jak asynchronicznie i nieblokująco.
To takie najtrudniejsze.
Takie najtrudniejsze właśnie, które, a jednocześnie fajne, tak.
No, jak najbardziej. Które już nie do końca są po prostu poziom niżej. Są już bardziej takim technicznym, które już dają nam taką sugestię, jak mamy zaimplementować te cztery cechy, które ten manifest tam chce przekazać. No bo to, że coś jest responsywne, możemy sobie na 1000 i jeden sposobów zaimplementować. Większość nie do końca responsywnych. Pozostałe cechy również, no to są takie bardzo ogólne wymagania, ale na pewno wpisujące się w współczesne aplikacje, gdzie mamy jednak dużo kanałów dostępu, dużo, dużo wszelkich maści informacji, które płyną i dużo sygnałów, na które musimy reagować też.
I dużo miejsc, gdzie to wszystko się może wysypać.
No, tak, tak. Bo to też ze sobą pociąga, oczywiście, dużo miejsc takich. Więc właśnie takie są założenia ogólne, a diabeł, jak zwykle, tkwi w tych szczegółach implementacyjnych. No i teraz jak mamy sobie takie coś zaimplementować, jak mamy taką reaktywność zrobić i aby z czym też w ogóle ta reaktywność tam ma się kojarzyć? No bo jeśli mówimy asynchronicznie, no to, no to okej. Asynchronicznie zrobimy sobie. No, w Javie na przykład Future, CompletableFuture, może jakiś wątek gdzieś tam się pojawił. Mamy synchronicznie i czy to już jest?
Zaskakujemy, bo jakoś, albo coś, coś tam executor.
No i mamy już o. Asynchroniczność nieblokująca? Tu już może troszkę będzie takich więcej pytań. To aż nam się zablokuje. Do montażu się zablokuje. Tutaj może to już nie będzie takie oczywiste, gdzie ten aspekt nieblokowalności ma, ma wystąpić. No bo jednak jesteśmy przyzwyczajeni do takiego stylu programowania. Po prostu wykonujemy operacje linijka po linijce i nawet nie zastanawiamy się, że w każdym z tych linijek jednak możemy natrafić na operację blokującą. Na dysku czy na bazie danych.
Wyzwania programowania reaktywnego: Callback Hell i strumienie danych
Zanim może dotrzemy do tego nieblokowania, to jeszcze a propos tego właśnie toolingiem pisania linijkę po linijce. Bo jakbyśmy chcieli w takim stylu to pisać, to nasze reaktywne cacko, to szybko ugrzęźniemy w ifologii takiej, że, że głowa. Bo no, tak. Nie, bo kto kiedykolwiek próbował ogarniać więcej niż kilka statusów jednocześnie w takim kodzie, to jest sobie w stanie zwizualizować, że to się sprowadza do coraz częstszego odpytywania o stan. To to przypomina taką wycieczkę z dziećmi gdzieś tam na daleko, nie? Gdzie w pierwszej minucie pada pytanie: „Daleko jeszcze?”. I tak przez 600 km za minutę. Nie? Więc tu mamy takie odpytywanie o ten stan, czy to już? Nie? Czy ten obiekt już gotowy, czy tamten już gotowy, czy, czy ten, czy tamten, czy ten i tak w kółko. Nie?
A gdzie czas na naszą logikę?
No właśnie, więc to nie jest ta droga. Pomijając jakby też dużą upierdliwość takiego odpytywania, mamy również oczywiście ten aspekt blokowalności takiego odpytywania. No bo teraz kilka Future’ów, które chcemy rezultat jednego wykorzystać w drugim, no to nagle się okazuje, że mamy niby kilka teoretycznie niezależnych calli, ale jeśli chcemy mieć wynik i użyć go w ponownym, to musimy zrobić get i już mamy pracę blokującą. Może trzeci będzie wykorzystywał pracę z drugiego. Więc kolejny get. Więc tak naprawdę nic nie zyskujemy. Właśnie tutaj chcemy mieć operację ładnie powiązane w jakiś no, taki, jak już wspomnieliśmy, flow. Jakby naszkicować przepływ informacji, przepływ tu naszych takich, no, eventów, zdarzeń danych w czasie, ale chcemy to zrobić troszkę inny sposób. No i tutaj ten przepływ to może doprowadzić do jakiegoś nawet strumyka czasem.
No to się zaczyna od takiego niby niewinnego przepływu, a tu nagle płynie, że Jezus Maria. No tak.
No bo to generalnie jest troszkę opis takiego strumienia danych, na którym chcemy mieć pewne operatory. Te, no, wprowadzając też troszkę domen klawiatury. Event stream tam się pojawia takie pojęcie. Tak. Czy może zwykły Java stream? Czy to też się do tego będzie nadawało? To sobie powiemy dalej. Jaka jest może różnica pomiędzy takim streamem a takimi lepszymi, no, niekoniecznie lepszymi, innymi strumieniami. To nikogo nie obrazić, co nie?
No tak, tak. Wszystko jest do odpowiednich zastosowań, naturalnie. Jest taka drzewowa poprawność albo ogólnie…
To zależy.
No i tak, no i, no i to są takie aspekty już bardziej, bardziej techniczne i rozwiązań do tego i jakbym rzeczy, które możemy użyć i też sobie zrobić na piechotę pewne rozwiązania, jest też sporo. Wspomnieliśmy właśnie o tym, o tym Future. Mamy też podejście i rozwiązania oparte na CompletableFuture, czyli też na takich callbackach. I tu już jakby troszkę unikamy może tego przedwczesnego blokowania, bo możemy sobie w takim callbacku, czyli w środku już, jak już dostaniemy odpowiedź z jakiegoś serwisu czy blokującego innego zapytania, możemy sobie już to wykorzystać. Ale no, też następuje taki troszkę callback hell tak zwany, że te callbacki nam zaczną się bardzo mocno przeplatać i trochę ciężko i trzeba jakoś zsynchronizować. Pewnie by było coraz ciężej już to jest ogarnąć. Więc to też tak nie do końca jest zarządzalne, przynajmniej jaki mamy już trochę więcej poziomów tutaj tej naszej asynchroniczności i chcemy nie blokować w troszkę więcej aspektach.
No właśnie, więc te rozwiązania i ogólnie jakby takie techniczne implementacje i jakby rozwiązania tych problemów dość wcześnie pojawiły się u na froncie, zwłaszcza chyba jednak na froncie w takim wydaniu troszkę SPA, takim Single Page Application. No bo mówimy też o takich, o przepływie danych, czyli normalnie takich mamy sobie takie klasyczne backendowe kontrolery. No to one najczęściej są krótko żywotne, no bo jednak mamy zapytanie, odpowiedź i rzekać transakcja pod spodem często sobie tam żyje jeden jakiś krok tego naszego przepływu. Te strumienie one nie są aż tak bogate, przynajmniej w takiej większości w takich, można powiedzieć, crudowych prostszych aplikacji, więc często nie ma aż takiej potrzeby, żeby jakieś skomplikowane obróbki tych strumieni wprowadzać. Natomiast na froncie, zwłaszcza właśnie w tym wydaniu SPA, kiedy mamy komponent, który żyje na stronie dość długo, reaguje na zdarzenia użytkownika, reaguje, bądź potrzebuje jakiś danych z serwera, no to tutaj jakby to zapotrzebowanie i taka, taki techniczny wymóg, żeby móc w jakiejś fajny sposób te eventy zagregować, jest trochę, trochę większy.
I właśnie ja pozwolę sobie tutaj przykład może zaproponować i niech ten komponent nasz zbiera jakieś metryki z czegoś, nie? I one może odświeżać sobie w miarę, jak te dane przychodzą, wykres, który tam sobie rysuje, nie? I to jest taka, taka rzecz, która wiadomo, że musi cały czas żyć, bo cały i cały czas jest zasilana tym strumykiem właśnie tych danych, nie?
Tak, tak. No to właśnie może być też kombinacja w ogóle wielu strumieni. Bo to może być, no, tak, tak. Coś możemy sobie jakieś operatory na tym stosować. Dane przychodzą z serwera, mogą być dane, które użytkownik gdzieś tam sobie wklepuje w danej formatce. No, tutaj jakby widać, że ten cykl życia i źródeł danych może być dużo i one są rzeczywiście rozciągnięte w czasie i te komponenty są troszkę inaczej się rozkłada ten cykl życia. Więc rzeczywiście, no, tutaj ciężej stosować jest takie klasyczne, klasyczne metody. To po prostu prowadzi do takiego zezwierzęcenia kodu, że ciężko to ogarnąć.
Nie? Większe nawet niż zwykle. Niż zwykle. No, nie chciałem to na backendzie też miewamy. No, zdarza się, no, najlepszym się zdarza, ale no, wiadomo, że właśnie tego typu zastosowanie, tego typu wymagania właśnie to widać tutaj, że, że to narzuca trochę zmiany tego sposobu myślenia. I teraz musimy się tak przerzucić na tą jakby drugą część, w sensie, tą przeciwwagę do klasycznego podejścia, czyli nie, że my kontrolujemy wszystko synchronicznie i czysto w jakiejś tam określonym, w jakimś porządku, tylko asynchronicznie, dostając sygnały czy eventy, jak to tam nazwiemy. Nie wiemy, kiedy się ich spodziewać. One mogą przyjść w dowolnym momencie, mogą też nie przyjść, nie? A my na przykład chcemy czekać na coś, ale to nie przychodzi. Nie? I to też jest, to też jest wyzwanie, z którym oczywiście tam systemy wcześniejsze, starsze, tradycyjne się mierzyły i jakieś tam timeouty były i to już wtedy wymagało sterowania troszkę odmiennego. Nie? Jeżeli chcieliśmy zrobić coś z jakimś timeoutem, to musiał być pod spodem ktoś, kto tym czasem zarządza, kto zmierzył nam ten czas. I jeżeli przekroczyliśmy, no to po prostu się urywało. Nie?
Tak, zawsze jest jakiś tam taki element asynchroniczny w takich rozwiązaniach. No bo jak się tego pozbawimy, to mamy przetwarzanie batchowe. Wszystko leci po kolei i trwa do rana. No tak, tak. No właśnie, no tutaj już jakby kręcą się wokół tego technicznego rozwiązania, to możemy chyba jasno powiedzieć o kilku frameworkach czy implementacjach właśnie takiej reaktywności, które tutaj mamy na myśli. No i na przykład taki ReactiveX i wzorcach.
I wzorcach. Tak, bo to są generalnie pewne rozwiązania, które implementują te wzorce. I właśnie ReactiveX jako taki trochę umbrella project, który dostarcza implementacji dla kilku języków właśnie takiej reaktywności, jest dość fajnym przykładem, bo tam jest i taki dość znany na froncie RxJS. Jest też wersja javowa RxJava i jeszcze dla kilku innych języków w C++.
Nie jest na przykład RXCPP. Tak, RXCPP. Więc tutaj można sobie patent łatwo zauważyć, jak to, jak to jest stworzone.
Gdybyśmy język dopiszmy RX na początku.
I dokładnie. Więc assembler.
Oj, to by było chyba trudne i kto by tego używał?
No, to John, by to „Quake’a” przepisał. W każdym bądź razie, właśnie takimi pryncypiami tego całego ReactiveX-a, już, już ogólnie, bo generalnie jakby koncepty są dla wszystkich języków podobne, tylko wiadomo, są odpowiednie implementacje. ReactiveX natomiast wprowadza coś takiego jak Observer, czyli…
Observer Pattern i strumienie danych w aplikacjach reaktywnych
Observer, który może coś obserwuje.
Był, który może czemu brzmi tak. I to jeszcze warto dopowiedzieć, że ten Observer to w ogóle jest wzorzec. Tak. To jest taki patent. Klasyczny design pattern. Tak, tak. Tutaj jest delikatnie gdzieś tam wzbogacono o pewne rzeczy, natomiast ten Observer może właśnie, jak nazwa wskazuje, obserwować pewne źródła danych. I te źródła danych mogą być albo statycznymi, statycznymi zupełnie listami, czyli Observable.just i możemy sobie zrobić listę elementów. Mogą to być…
Mogą to być, czyli to może być jakiś API, z której osoby nasłuchujemy, czy nawet możemy się bardzo w łatwy sposób podpiąć na przykład do listenerów elementów HTML-owych i na przykład obserwować ruchy czy kliknięcia myszką. Więc to fajny sposób na stworzenie takiego observable z różnych źródeł danych. Ale to, że możemy sobie coś takiego zrobić, to jeszcze jest jakbym niewielka korzyść. No bo to, że możemy obserwować, no to OK, fajnie, ale musimy mieć to, co jest, co się nadaje do obserwowania.
No właśnie. Co ten… przetłumaczyć, ale jakoś tak nie pykło.
Co ten Observer nam daje? Otóż on nam fajnie pokazuje jakby przepływ i daje nam dostęp do tych eventów, tak nazwijmy to ogólnie. No bo to może, może tak ogólnie powiedzieć, że to będą jakieś eventy, które dane źródło emituje.
Które przez niego przepływają.
Które przez niego przepływają. My możemy sobie się na to zasubskrybować, dostając powiadomienie o każdym nadchodzącym evencie, ale możemy sobie dodatkowo jeszcze poprzez szereg różnych ciekawych operatorów sterować przepływem danych w takim w takim operatorze, lub też reagować, bo mamy też, mamy też odpowiednie callbacki na czy to na błędy, czy na zakończenie emisji danych w strumieniu. To są też, czy chociażby wspomniany timeout, jeżeli na przykład spodziewamy się, że coś ma być, a nie ma. Tak, no to wtedy nie będziemy z nim pisać do rana, tylko po prostu z timeoutem zakończymy i powiemy, że był błąd i tyle. Tak.
No, Observer bo możemy sobie oczywiście robić wiele różnych operacji. Czy to mapowania, czy generalnie też agregacji różnych innych obserwacji, możemy se otworzyć takie bardziej skomplikowane, skomplikowane właśnie strumienie nasłuchujące z różnych, z różnych źródeł. No i dzięki temu możemy sobie w fajny sposób opisać flow naszych danych, które w danym momencie nas interesują i reagować faktycznie w asynchroniczny sposób, w momencie, kiedy dana rzecz się pojawia w źródle, a jednocześnie przyjdź osobie do nas, do naszego, do naszego Observera. I to jest właśnie ten element asynchroniczności. Czyli mamy właśnie ten nieblokowanie, mamy, mamy przetwarzanie tych danych w momencie, kiedy on się, one pojawiają. I to jest właśnie ten, ten taki techniczny aspekt już tej całej reaktywności. No i to, że to, że to jest jednocześnie te same koncepty możemy też użyć na froncie i na backendzie w różnych językach. No to też sprawia, że jednak o ten kod wygląda troszkę, troszkę bardziej znajomo, jeśli robimy obie strony jednocześnie.
Tak, tak. Właśnie różne frameworki, różne języki, a to samo podejście, bo po prostu zestaw wymagań jest podobny i tu i tu. A myślę, że fajnie genezę można zilustrować tak, jak to widziałem w jednej książce, właśnie, notabene do opisującej RXCPP, gdzie tam autorzy w genezie przytoczyli po prostu systemy okienkowe. Po prostu takie menedżery okien, nie? I analogia do tej klasy zagadnień jest tutaj bardzo widoczna, bo jak się weźmie na przykład w kontraście do tego, to, co królowało wcześniej, zanim się takie właśnie okienko wartości i pojawiły przeróżne, a tutaj mówimy nie tylko o Windowsie, mówimy o tym X-Window System, tylko X-ach i o jeszcze różnych. No, na przykład nie tylko na platformie X86, czy tam AMD 64, czy ogólnie no, pochodnych X86, bo na RISC-owych jeszcze właśnie X-y królowały już za czasów QNX-a. I Amiga nieśmiertelna miała przecież swojego Workbencha. On był wielozadaniowy.
I dokładnie z tym samym się mierzył i świetnie mu to szło, nie? A przed tym wszystkim były sobie takie po prostu wsadowe, jakby, nie? Dosyć powiedzmy. Nie bójmy się historii. Nie, no, czyli tak, jak się programowało pod takiego, pod takie synchroniczne stare coś, no to można tam było zapuścić sobie jedno zadanie i tyle, nie? System operacyjne rozwinęły się od tamtego czasu i poszły w wielozadaniowość, bo właśnie zaobserwowano, że takie przetwarzanie wsadowe to to tak nie podoba się. No i jak już się pojawiły wielowątkowe, wielozadaniowe jakieś systemy z wywłaszczaniem, gdzie AmigaOS był jednym z pierwszych takich zrobionych fajnie, że działał w sensie i to zapewniał płynność tej małej Amidze, przypominam, w kontraście do dużych blaszaków już wtedy na DOSie, czyli zaobserwowano, że ta wielozadaniowość daje się przenieść na UI właśnie w takiej eventowej, message-driven dokładnie postaci.
Bo każdy taki system wykorzystuje zdarzenia, które, no właśnie, pochodzą albo z tych sygnałów od użytkownika, albo od innych komponentów systemu. Coś nam tam generuje strumień tych zdarzeń i taki system okienkowy jest właśnie takim już takim reaktywnym systemem, bo jest sterowany zdarzeniami, sterowany komunikatami, message-driven, musi być resilient, no bo jak nam zgaśnie jedno okno, to to odpalamy znowu. No i reactive jest, no bo reagujemy na to, co się dzieje. Nie? On tam to już jest kwestia oczywiście implementacji, ale no, nie spodziewamy się bardzo dużo jakiś aktywnych busywaitów, takich aktywnych pętli, które co tam sprawdzają i odczytują po prostu z rejestru, czy to już się zdarzyło. Tylko mamy asynchroniczne systemy peryferyjne, na przykład w komputerze, tu trzymając się tej, tego przykładu, które nam te zdarzenia generują wtedy, kiedy one występują. Nie? Czyli sterownik takiej myszy generuje nam zdarzenia o tym, że kursor ma się przesunąć. Czyli po prostu poruszono ten, czy tamten manipulator.
Różnice między strumieniami reaktywnymi a kolekcjami w Javie
Nie, tak w ogóle chyba UI to nawet nie tylko taki UI webowy, który mamy teraz, ale o ten, jak desktopowy. Ktokolwiek, kto robił właśnie takie standardowe desktopowe, czy to SWT, czy w jakimś Delphi, czy, czy w MFC, no to klasycznym, klasycznym, klasycznym jakimś tam event handlerem był na przykład button.onClick. No i to też jest pewien rodzaj takiej reaktywności, bo pisaliśmy handlera na odpowiednie akcje tych naszych komponentów. On ma onClick i wszelkie inne listeners, które dany komponent wystawiał i w nich zawieraliśmy tą logikę, która miała się wydarzyć na te zdarzenia. Reagowaliśmy na te zdarzenia, które na tym komponencie sobie implementujemy, no i ten komponent udekorowany tym, tym kodem, rzeczywiście pełnił potrzebne nam, nam funkcje. One były zawarte w tych listeners w danym komponencie, no i tam sobie, tam sobie siedziały.
To dokładnie, a poczciwy Main to był gdzieś tam totalnie schowany. Się nie przejmowaliśmy i to nas nawet nie interesowało, jak to pod spodem działa. My wiedzieliśmy, że ten kawałek tego naszego listenera wykona się tylko wtedy, kiedy ten przycisk będzie kliknięty. On nie będzie gdzieś tam wykonany wcześniej czy później, czy nie przejmowaliśmy się tym w ogóle. To to była ta reaktywność i to oddzielenie tego zachowania od tego, gdzie to zachowanie jest zaimplementowane.
Właśnie jeszcze było łączenie takich zachowań. Bo na przykład, jak mamy okno i chcemy sobie nim narysować w nim kreseczkę. I ta mysz nam sobie po tym oknie pomyka. Nie? To nie dość, że, że dostajemy informację o tym, że ona tam jest, to jeszcze w którym miejscu. Czyli to po prostu ileś tam informacji się zbiega w czasie i one pochodzą z różnych źródeł. Bo, bo na przykład to, że kliknęliśmy lewy przycisk myszy, to jest zdarzenie od tego przycisku myszy, ale to, że mysz była, że kursor był na tym punkcie, to już jest zdarzenie z innego tam kontrolera. Nie?
Tak, bo to oczywiście też zagregowane są jakoś te eventy i rzeczywiście mamy najczęściej jakiś parametr do danego naszego eventu, żeby wiedzieć coś więcej na temat zdarzenia, które właśnie obsługujemy.
I elparam.
No, dokładnie. No, ale to też, żeby to zaobserwować i sobie tak jakby, no, się, no, poczuć to po prostu, to najlepiej jest sobie napisać taki najprostszy, najprostszy program windowsowy, który obsługuje zdarzenie z myszy i pomachać myszą po ekranie. Nie? To wtedy widać po prostu, jaki jest duży strumień tych zdarzeń. Czyli, że faktycznie ten komputer biedny to jednak musi się natrudzić, żeby nam coś sensownego z tego wyłuskać. Nie?
No, to samo możemy zrobić na froncie właśnie za pomocą Observera. Tam też sobie zobaczymy na konsoli szereg, szereg, przykład na X. No, wiadomo.
Albo jeszcze na Amigę. Smooth na desktopie zawsze lepsze, niż ta Francja.
Więc tak, no widać, że front, nawet taki desktopowy, był już poniekąd prekursorem tego typu rozwiązań. No, przy obecnych backendowych, takich czysto backendowych rozwiązaniach, gdzieś tam tego miejsca pewnie na to, nałożyć jest trochę mniej. Dlatego chyba troszeczkę to zostało, że tak powiem, w mniejszym użyciu. No, ale też, no, biorąc pod uwagę coraz większą odpowiedzialność aplikacji, coraz więcej tych use case’ów też na to, na to będzie, żeby te strumienie obrabiać. Ale są, bo wspominaliśmy o takim przykładzie w serii „O” i w grach, żeby tam daleko nie szukać, gdzie mówiłeś sam, że to Final Fantasy. Tak zrobili sobie taką warstwę podglądającą w ogóle cofanie wszystkich, tak? Taki audit log do tego, co się dzieje tam w środku w silniku. Nie? I to doskonały przykład jest na to, bo tych strumieni różnych zdarzeń z silnika pewnie im tam płynęło całą masę, a musieli to jakoś obrabiać i agregować, pewnie łączyć. No, przetwarzać ogólnie te dane, żeby w jakiś ogarnięty sposób to wyświetlać i pewnie dobrze by to było robić jeszcze w takim, no, w miarę rzeczywistym czasie. To oczywiście jest wszystko do uzgodnienia. No, tak, jak to się tam ma dziać, ale żeby to miało jakiś sens. Nie można było na bieżąco analizować te dane. Przynajmniej tak.
Tak, bo tu mamy rzeczywiście do czynienia z dużym strumieniem danych, dużą ilością danych, więc faktycznie musimy to obrabiać gdzieś w jakiś osobnym, osobnym komponencie, osobnym miejscu, gdzie odciążymy główny, a główny proces od tego. Natomiast tak sobie patrzę teraz nawet na tego RxJava, czy, czy ogólnie na rozwiązanie. Ktoś sobie zobaczy, że, no, dobra, no, to jest jakiś stream, tak. Observable. No, ale ktoś może pomyśleć: „No, dobrze, ale ja używam strumieni w Javie. Mam Javę ósemkę i dobrze. Mam tam oczywiście, mam tam oczywiście kolekcję, mam jakiegoś iterable’a. Robię tutaj, proszę, map jest, jest for each. No, to, to ja też jestem reaktywny. No, to też jest strumień.” I czy ten strumień jest, jest tym samym, z takim strumieniem w rozumieniu reaktywnym?
I właśnie ciekawostka: nie do końca.
Nie do końca tak. Podejrzewałem, tak. To jest pytanie tendencyjne i oczywiście retoryczne. Z wyjaśnieniem. Jedno i drugie rozwiązanie rzeczywiście ma do czynienia z pewnym strumieniem danych. Czyli elementy przychodzą gdzieś do strumienia z jakiegoś źródła. Mamy ciąg operatorów, czy to filtrów, czy to, czyli jakieś odrzucających, czy to map, które transformują nam obiekty na następne obiekty. I możemy sobie dalej gdzieś tam dalej sobie obrabiać i na końcu mamy jakiś tam wynik. No, w przypadku strumieni javent to najczęściej kolekcja, czy mapa. Robimy sobie z tego jakiś wynik, czy po prostu robimy find jakikolwiek element, jeśli chcemy sobie znaleźć w szczególności. Tak, redukujemy to do jakiegoś pojedynczego, pojedynczej wartości, która nas interesuje. Tak, czy to będzie zagregowana, czy jakaś jakiś boolean.
Tak. Natomiast Observable w takim rozumieniu właśnie takim reaktywnym, są pewne charakterystyki, które, które jednak mówią, że troszkę inaczej to działa. W sensie, strumienie javowe są bazowane na pull-based, a strumienie Observable, czyli takie reaktywne, są push-based. I co to oznacza tak naprawdę? Źródło danych w reaktywnym, jakby, podejściu to ono, jakby, steruje wysyłaniem danych. To ono wysyła dane do naszego, do naszego observable’a, czyli ono, jakby, jest tym czynnikiem, który sprawia, że dane pojawiają się w strumieniu. A observable jest tylko, o tylko, nasłuchuje na tym. Nic więcej nie robi.
On jest tym właśnie, kto reaguje. On jest, on jest tym… znaczy, powiedzmy, to, co wykorzystuje tego, co obserwuje.
Natomiast w przypadku, w przypadku strumieni javowych mamy do czynienia z metodą pull, czyli to tak naprawdę operatory pobierają sobie kolejne elementy i procesują linię w odpowiedni sposób. Ale źródło, jakby, danych nie ma tutaj żadnego znaczenia, nie steruje przepływem, nie ma możliwości nam zatrzymać jakiś danych, nie ma możliwości żadnego sterowania tym naszym strumieniem. Albo ma dane, albo nie ma. Albo ma dane, albo nie ma. Czy to taki prosty bardzo pojemnik na dane. Tutaj nie mamy za bardzo możliwości kontroli. No i z tym też jest związany jakby też sposób samego przetwarzania, no bo jednak strumienie w tym momencie takie javowe są synchroniczne, a te strumienie z observable są asynchroniczne. I tutaj też, tutaj jest też ta różnica. I to powoduje też pewne takie efekty, że strumienie javowe tak naprawdę są, pomijając, że jakby sam fakt, że niektóre operacje są terminalne, czyli jeśli wykonamy pewne operacje na przykład zrobimy z for each’a, czy na końcu sobie zmapujemy do listy, tak naprawdę kończymy już strumień i on jest uznawany jako zakończony. W sensie, wszystkie elementy zostały przepracowane. Nie możemy po raz kolejny się do niego podłączyć.
Dostajemy wtedy illegal state exception. Jest nieaktualny, proszę stwórz sobie nowy strumień.
Natomiast strumienie w takim reaktywnym rozumieniu w observable możemy subskrybować się do nich wielokrotnie, czyli to jest ten nasze źródło danych, które będzie te dane sobie tam emitować, obrabiać i my możemy w kilku miejscach się do nich podpiąć, nasłuchując na dane.
Z kilku miejsc to żyją. Jednak one tak jakby żyją. No, są takie bardziej…
Tak, bardziej żywotne rzeczywiście. Więc tutaj to też sprawia, że strumienie javowe mają dość ubogą ilość operatorów. Tak naprawdę, jak sobie zobaczymy na listę ReactiveX, to tam jest tego dość dużo. Zwłaszcza te operatory, które manipulują nam troszkę czasem, czyli możemy sobie nakładać pewne okna, czy pobierać tylko jakieś, skipować pewne elementy. No, tutaj tych ilość operatorów, ilość operacji, które możemy wykonywać na takim strumieniu observable, jest znacznie bogatsza. No bo ten model, który pod spodem zarządza nam tym strumieniem, jest, jest troszeczkę inny. I stąd się bierze też ta różnica.
Tak naprawdę. No i tak właściwie, jeśli można, to można. Dzięki. To te strumienie javowe to jest właśnie taka nakładka niewielka na API kolekcji, głównie do tego, nie?
Tak, właśnie. To jest lambdy się pojawiły, no to się pojawiły też razem z nimi strumienie i no, jest takie fajniejsze API. Można sobie sprytniej te rzeczy. Już tak naprawdę bierzemy sobie elementy, mapujemy, filtrujemy, coś tam redukujemy do pewnego stanu, ale tak naprawdę to nie służy, to nie opisuje nam tego procesu przetwarzania tych danych w czasie. Tutaj, jakby, niespecjalnie. Nie mamy tutaj message-driven. Tu tak, tutaj niespecjalnie występuje ten koncept czasu w strumieniach javowych. Tu mamy bardziej koncept transformacji pewnego źródła danych. Oczywiście źródło danych, strumień, nie może być nieskończone, bo dopuszczamy, jakby, nieskończone strumienie. No, chociażby strumień generujący liczby na przykład. Tak. Możemy sobie, możemy sobie to zrobić. To może być też strumień, który zrobimy sobie parallel, czyli on też będzie, jakby, rozłożony na więcej zrównoleglony, ale tak naprawdę to nie, wciąż nie opisuje nam w żaden sposób, jakby, upływu tego czasu. To jest wciąż tylko dopływ kolejnych elementów, które sobie transformujemy, mapujemy, lecimy sobie dalej. I właściwie tyle. To nie jest byt sam w sobie, do którego później możemy się podpiąć z różnych miejsc, reagować na niego. Możemy się odsubskrybować i ten byt wciąż będzie istniał.
To to tak jeszcze aspekt właśnie tej odporności na błędy, resilience. Jeżeli nam się w trakcie tego przetwarzania takiego strumienia javowego sypnie, no to tak jak już powiedziałeś, co najwyżej możemy sobie jeszcze raz ten strumień tak zrobić, nie? A nie to, że możemy się, możemy zareagować na ten błąd, przeprosić: „Sorki” i wracamy do przetwarzania. No bo to już nie żyje wtedy, nie?
Dokładnie. To jest też to bogactwo operatorów. No, w sumie i ogólnie w observable bo mamy onCompletion, onError. Czyli możemy, żeby zareagować na pewne zdarzenia cyklu życia tego strumienia. Czyli to też pokazuje, że jest on troszkę, ma trochę bogatszą semantykę. Więcej się tam dzieje i możemy na więcej rzeczy zareagować. No, chociażby właśnie ten jest dość przydatnym i niekoniecznie wtedy musimy przerywać. Możemy zareagować na, na pojawienie się, bo za chwilę będą takie eventy w tym strumieniu naszym, że spokojnie sobie przetworzymy, nie? A tego konkretnego przypadku nie dało się z jakiegoś powodu.
No, tak. A więc, więc tam gdzieś trafił do loga. No, tak, więc, no, tutaj widzimy, że, no, jest pewna różnica. No i widzimy też, że ten manifest ma trochę sensu jednak.
No, trochę ma. Trochę ma. Chociaż nie mówi, nie mówi o szczegółach, jak to wszystko zrobić. Jak to zwykle manifest jednak.
Wymyślicie manifest i róbcie sobie. Róbcie sobie. Każdy sobie to robi w inny sposób. No bo właśnie, jak to, jak to robić tak naprawdę? Bo OK, ktoś sobie teraz powie: „Dobra, no to jednak fajnie, ten Reactive. No to robimy fajnie, ale jak to my zrobiliśmy kiedyś właśnie, miałem taki bibliotece fajnej.” Właśnie pytanie, jak to zrobić i w którym miejscu to robić i tak naprawdę. Bo jeśli byśmy chcieli mieć naprawdę reactive, takim, w takim rozumieniu end-to-end, to de facto potrzebujemy całego reactive stacka najlepiej. I poprzez tak mam na myśli reaktywny dostęp. Pozwolę sobie jeszcze więcej, oczywiście, bo jeszcze w ogóle potrzebujemy takiego problemu do rozwiązania, w którym to jest w ogóle punkt wyjścia. Tak, w dzisiejszym odcinku tak jakoś sobie tak już chyba rutynowo pominęliśmy ten punkt: „Na co? Dokąd?”. Tak? Znaczy, chociaż on tam się pojawiał. Już go tam zasygnalizowaliśmy przez te wszystkie rozważania, nie? Że jednak, no, właśnie do takich zastosowań. No, ale to tak jak z CRUDem i jego lepszymi kolegami pod kątem wzorców, nie? Jeżeli mamy wymagania, które doskonale się da opisać CRUDem, robimy CRUD i coins. Nie rozważamy niczego więcej.
Wybór podejścia reaktywnego: kiedy i dlaczego?
Natomiast, jeżeli mamy takie wymagania, że wiemy, że asynchronicznie będzie się działo, że mamy być odporni na błędy, że jeszcze w dodatku jak nas ktoś zapyta, czy czytaliśmy manifest, to mamy powiedzieć, że tak. Tak, no to wiadomo, mamy go nad łóżkiem.
Dokładnie. To w ten czas go sobie implementujemy. To właśnie może jeszcze przybliżmy ten element, do czego to użyć, bo technicznie OK. Tak, to są wszystkie ważne powody, ale jakby jeszcze idąc dalej technicznie. No bo ta asynchroniczność i nieblokowalność to jest bardzo, to są bardzo potrzebne cechy systemu, jeśli mamy bardzo dużo requestów, użytkowników, ogólnie processing jednocześnie, bo chcemy wtedy optymalnie wykorzystać nasze zasoby, czy to pamięć, czy, czy właśnie cykle procesora, żeby tych użytkowników maksymalnie szybko obsłużyć. No bo w takim tradycyjnym modelu, jak mamy sobie tą Javę, no i wiadomo tam mamy, mamy thread per request, czyli dla każdego requestu mamy osobny wątek. Standardowo 100-200 wątków, nieważne, ileś tam ich sobie mamy, no i każdy wątek sobie elegancko obsługuje każdego użytkownika. Natomiast jeśli tych użytkowników jednocześnie będzie bardzo dużo, no to wiadomo, że to w opisie pula wyczerpie i requesty będą musiały czekać na zwolnienie wątków. A w każdym z tych requestów oczywiście mogą się dziać różne ciężkie rzeczy, właśnie jak oczekiwanie na zwrot z bazy danych, czyli operacja blokująca, odczytanie pliku, operacja blokująca. I każdy z tych wątków będzie czekał grzecznie.
Dokładnie, czy też odpytanie jakiegoś tam gdzieś tam socketa, bo właśnie strzelamy do kolejnego API, który to też jest teraz przeciążone, bo tam wszyscy chcą. No, dokładnie. To jest ten problem. A tak naprawdę te wątki, które teraz czekają na te blokujące operacje, one mogłyby już obsłużyć innych użytkowników, a może ci inni użytkownicy to akurat potrzebują tylko wcale z tego stringa skądś tam.
Nie tak łatwo przełącza się do wątki. Nie wiem. No, nie tak łatwo tam się powinien. Wyjątki robią. Nie tak łatwo, ale to jest też cecha tych reaktywnych systemów, żeby właśnie to przełączanie umożliwić. Bo to jest, to jest w ogóle całe clue, całe, całe sedno tutaj całego podejścia, żeby maksymalnie wykorzystać właśnie dostępne zasoby sprzętowe. A to możemy zrobić faktycznie, jeśli minimalnie blokujemy na właśnie operacje blokujące i przeprowadzamy nasze rzeczy asynchronicznie. No i jeśli mamy tego typu system, czyli właśnie wysoką współbieżność, duże właśnie użycie zasobów, wtedy rzeczywiście możemy rozważyć tego typu podejście. Tylko no, tak jak wspomnieliśmy, no, raczej to już musi być pełen stack asynchroniczny, niż tylko zwykły. Na przykład zobaczyliśmy pięknego WebFluxa w Springu, no to dobra, to teraz przepiszemy wszystkie kontrolery na Flux i na Mono i będziemy, że jesteśmy reactive.
I powiemy, że jesteśmy reaktywni. A pod spodem tak naprawdę dostęp do bazy mamy blokujący. Nasze API, które jest blokujące, ale końcówka, nasz REST controller, jest pięknie nieblokujący. No, to nam niekoniecznie może przynieść spodziewane efekty. Co więcej, w przypadku takiej zwykłej aplikacji o niejakiejś wielkiej współbieżności i nie jakimś wielkim ilością użytkowników, takie podejście może nawet delikatnie spowolnić to, bo jednak narzut na konstrukcji pewnych elementów właśnie reaktywności może być, może mieć jakiś wpływ, minimalny versus zwykłe podejście. Bierzemy encję z serwisu i wysyłamy jako response i tam nie bawimy się żadną reaktywnością.
No, tak, ale to właśnie znowuż ten wiadomo, ten zawsze, co, co zawsze tkwi w szczegółach. Takich w tym wypadku, bo nawet jeżeli weźmiemy cały ten stack, a jednak nie przemyślimy tego, co pod tym stackiem siedzi, nie? Czyli tego, w jaki sposób my tą domenę naszą zorganizowaliśmy i obsłużyliśmy, to nie za dużo nam to da. Bo tak, bo jeżeli na przykład będziemy mieli wszystkie klocki, powiedzmy, infrastrukturalne, które nam już tą responsywność dają, tylko czekamy i rezydentnie te błędy tam handluje i w ogóle wszystko pięknie jeszcze nam się to skaluje, ale jak generujemy ten, tą zawartość, w sensie generujemy odpowiedź i tam musimy widok poczekać, aż widok się w ogóle wyrenderuje na bazie, nie? A widok to jest zapytanie żywcem robione, czyli my odpytujemy jakieś query, które szybko ma założeniu dać nam dane, a tam która zaczyna grzać się do czerwoności i wszystkie procedury, zanim to wszystko poskładają, to to zajmuje minutę, to pozamiatane, nie? Czyli tutaj bez odpowiedniego narzędzia, swoją drogą te właśnie związane z tematem, ale sama architektura pod spodem też się prosi o przepisanie albo wręcz zaprojektowanie w taki sposób, żeby umożliwić to. Nie? Bo jeżeli faktycznie mamy wymaganie wysokiej responsywności, to by szybko musimy te odpowiedzi generować. A jak nie możemy szybko, to szybko musimy powiedzieć, że nie uda się. Sorry.
Tak, to tutaj, tutaj mam na myśli ten aspekt techniczny, czyli tą responsywność na razie tylko na takim poziomie technicznym, czyli mamy klocki, implementacje pewnych rzeczy, które technicznie nam to dadzą. Ale oczywiście, jakby nasz model, nasza domena, no, też musi to, muszą to wspierać. Oczywiście, jeśli to będzie nieoptymalnie napisane, no to tutaj żadna, żaden manifest i żadna święcona woda nie pomoże.
Co ona woda nie pomoże. To, to jakby oczywista sprawa. Natomiast, no, trzeba sobie też jasno powiedzieć, że jeśli chcemy wprowadzić ten reactive stack, no to trochę nam to namiesza tutaj w naszym podejściu, bo przez reactive stack rozumiemy warstwa dostępu do bazy danych. Czyli mamy też odpowiednie w Springu na przykład implementacje repozytoriów, które nie zwracają nam list, czy pojedynczych obiektów, ale zwracają nam Flux czy Mono, czyli wrappery na te nasze odpowiedzi. Mamy też implementację serwera, które też jest reactive w odpowiedniej wersji, żeby też Spring swoje kontrolery mógł, żebyśmy mogli w Springu swoje kontenery implementować jako Flux, jako Mono, czyli to wszystko też musi być cały stack. Oczywiście, jeśli mamy jakieś integracje, jakiś API, no to też WebClient, który też jest reaktywny. Więc wszystkie rzeczy, które gdzieś tam po drodze mogą nam wprowadzić blokowania, no, musimy jakby mieć w takiej wersji nieblokującej. Reaktywnej. I wtedy rzeczywiście można powiedzieć, że to nam przyniesie jakiś spodziewany, no, lepszy, lepszy efekt. Na pewno tylko jeden klocek, no to tutaj nam pewnie niewiele wprowadzi. No bo jeśli będziemy mieli zostawimy sobie repozytorium, które będzie reaktywne, ale gdzieś tam po drodze i tak będziemy robić, żeby ten wynik pobrać, bo coś chcemy sprawdzić, no to tak naprawdę tym mijamy się z celem. Nic się nie zmienia tak naprawdę.
Wyzwania debugowania i testowania aplikacji reaktywnych
Więc tutaj, no, na pewno będzie to miało wpływ na nasz sposób, sposób programowania. Trzeba sobie też jasno powiedzieć, że, no, jest pewien narzut, jeśli chodzi o takie, o taki model mentalny takiego oprogramowania. No bo to są troszkę inne struktury, troszkę inaczej trzeba myśleć o naszym flow samego programu, bo to sobie trzeba zamodelować właśnie jako taki upływ eventów w czasie. To już nie jest takie łatwe do wnioskowania linijka pod linijką i jedziemy operacja za operacją i w każdej z nich mamy wynik operacji poprzedniej. Co oczywiście też wpływa na takie rzeczy jak debugowanie. Debugowanie tego typu aplikacji też nie jest łatwe. No bo tak naprawdę mamy pozagnieżdżane wszędzie jakieś opakowania na te nasze klasy. No i nie zawsze da się w łatwy sposób podejrzeć, co tam w środeczku siedzi. Bo to się jeszcze nie do końca wykonało.
Nie znamy kolejności.
Nie znamy kolejności, nie znamy. To się nie ma jeszcze tego z tego, tego.
Zmaterializowało.
Dokładnie. Nie ma tego bodźca, który by już wykonał i triggerował faktycznie przepływ informacji. Więc trzeba sobie też jasno powiedzieć, że, no, jest to pewien narzut i nie jest to aż takie super proste. I jeszcze dodatkowo, jak chcemy to automatycznie przetestować, to testy jednostkowe do tego, do takich rzeczy asynchronicznych, to też czasem są takie, że zęby bolą. No, w czym to testować?
Spokój, spokój. Spring akurat ma tam odpowiednie kontrolery do adnotacje do testowania właśnie tego typu, no, generalnie WebFlux-owych kontrolerów i tego typu reaktywnych elementów. No, natomiast, no, mam, mówimy o Erise Javie. No, są też inne implementacje właśnie w Springu. Generalnie polega na projekcie Reactor, stąd też troszkę innym właśnie nazewnictwo. Tutaj nie mamy observables, a mamy właśnie Flux, Mono. Jest też jeszcze Vert.x, czyli taka dość znana biblioteka, a właściwie cały ekosystem do obsługi właśnie reaktywności. Tam to troszkę inaczej robione. Tam tworzymy tak zwane verticle, czyli rozszerzamy, rozszerzamy klasę Vertical, gdzie w metodzie start dodajemy naszą logikę, która ma być w danym momencie, jakby, wykonywana.
Więc to też troszkę inaczej nam modeluje. Tak, to jest kwestia właśnie, jakie podejście zastosowali autorzy tego, czy innego narzędzia, bądź też my, jak sobie będziemy chcieli napisać. Nie zapominajmy, że to się wszystko wywodzi z takiej właśnie eventologii dość grubej, gdzie się dużo dzieje. Tak jak właśnie w systemach okienkowych, skąd, no, pewnie największa inspiracja przypłynęła, zanim się pojawiły prawdziwe takie potężne źródła danych, jak na przykład jakieś strumienie metryk, czy czegoś tam z jakiejś wielkich systemów. Nie? No i taką na przykład eventologię też na różne sposoby można ogarnąć, nie? Jak, jak piszemy swoje własne handlery do takich zdarzeń, no to to też miewają różne kształty. Więc zamiast się dziwić, że to, no tak, że te handlerki tutaj nam się różnią jakąś tam trochę semantyką czy składnią, ale najważniejsze jest to, że właśnie w takim handlerku reagujemy na to, co się zdarza, co właśnie się zadziało. Nie? I my to musimy zareagować.
No, to możemy pójść w ogóle jeszcze dalej, bo tak naprawdę może zamiast próbować dopasować naszą taką zwykłą synchroniczną, blokującą aplikację do jakiegoś przetwarzania eventowego, może użyć rozwiązania, które jest dedykowane stricte do tego. Czyli na przykład wspomniany we wcześniejszych odcinkach Apache Flink, który właśnie idealnie do tego służy, czyli zapinamy się na jakieś źródło, dokładamy nasze filtry, transform maps, mapowania, mamy lokalny stan, mamy pełno rzeczy, dzięki któremu możemy strumień, bądź też batchowe rzeczy w tym Flinku właśnie osoby obrobić, przetransformować, wysłać do innego źródła, które może być, na którym będzie nasłuchiwał nasz inny jakiś processing pipeline. No i to już bardziej, bardziej na takim poziomie naprawdę już kodu i samej struktury w ogóle projektu i architektury nawet projektu. Bo wtedy już mówimy o takim zwykłym, to jest takie bardziej pre-pen filter, architektura. Tam już nie ma żadnej warstwowej, czy, czy czegoś takiego. Ciężko by było te warstwy znaleźć prawdopodobnie. Tutaj mówimy bardziej po prostu o rurze z filtrami, które nam do celów wyrzucenia nasze, nasze eventy.
No, tam we Flinku na przykład też jest bardzo bogata, bardzo bogate elementy odnośnie filtrowania też tych eventów w czasie. Możemy sobie zakładać okna, możemy sobie pobierać te elementy z różnych czasów, możemy agregować, możemy je zliczać. No, tutaj rzeczywiście to jest jakby dedykowane do tego typu rozwiązań. Natomiast, no, to jest też do konkretnych zastosowań przetwarzania eventowego stricte. Natomiast jeśli mamy w aplikacji tylko jakiś strumieniowego właśnie nawet, strumieniowego bardziej. Tak. Natomiast, no, w aplikacjach najczęściej możemy mieć jakieś tylko kawałeczek bardziej strumieniowy. Możemy mieć jakiś element naszej aplikacji CRUDowej, która z jakiegoś powodu nasłuchuje na API, bo potrzebuje mieć ciągle sygnały dostępne.
Wtyka do bazy, wtyka do bazy.
No, nasłuchujemy, nie wiem, kursy walut, czy, czy kursy. Agregator jakiś tam metryk po prostu, który też musi cały czas nasłuchiwać. Cokolwiek właśnie. Więc tutaj, tutaj to do tego się, no, pewnie, pewnie też nada. Więc, no, jest tutaj duży, jakby, duże spektrum zastosowań i duże, duże pole do zastosowań różnych frameworków i trzeba sobie dobrze.
Różnych fragmentów tego podejścia. Tak. Raz czerpiemy z szerszego zakresu, innym razem z węższego. Akurat to nam wystarczy. Nie? Najgorzej, jak dostaniemy error broken. To znaczy, że rura nam się złamała. Proszę złamała.
Najgorszy wyjątek. Bo to nie idzie skleić za bardzo.
Cały czas płynie. Cały czas płynie. Tak. No i, no i właśnie to są te wszystkie techniczne elementy, które, no, trzeba sobie dobrze dopowiedzieć. Dlatego też, dlatego też chyba tego typu podejście, no, nie jest aż tak bardzo powszechne. Jest trochę tych rozwiązań i frameworków, które to stosują. Spring właśnie też stara się nadążyć, nadążyć i ze swoim WebFluxem i Reactorem też w tą stronę podążyć. Mikroserwisy wiadomo, wywierają presję, żeby w ten sposób modelować nasze systemy. No, bo jest to dość naturalne.
Wtedy podejście, bo siłą rzeczy wtedy mamy tutaj. Skąd to się wzięło, nie? Ta już wracając, powiedzmy, odchodząc kawałek na bok od tych takich strumieni, stricte strumieni danych, które do nas płyną i próbujemy reaktywnie przetwarzać, to właśnie takie message-driven podejście wzięło się chyba z chętniejszego spoglądania na Domain-Driven Design i w ogóle na mapowanie tych procesów, które po prostu my, jako ludzie, robimy w tych naszych biznesach. I tam właśnie widać, że, no, nie siedzi sobie jeden ludek i nie klepie całego procesu po linijka po linijce z góry na dół, tylko jest ileś tam punktów w tej naszej domenie, które generują nam właśnie jakieś komunikaty. To niekoniecznie muszą być zresztą, wspominaliśmy wielokrotnie, same eventy, czyli coś, co się zdarzyło i sygnalizuje zmiany stanu. To mogą być triggery do tej zmiany stanu, czy jakieś tam komendy, czy innego rodzaju komunikaty, które po prostu jakiś sens biznesowy i domenowy mają. A wszystko to płynie po prostu gdzieś tam sobie tym jakimś nawet wirtualnym strumieniem. My sobie tylko wyławiamy z tego strumienia odpowiednie rzeczy na odpowiednich punktach, nie?
I jeżeli sobie wyobrazimy taki system oparty o event sourcing, już niekoniecznie na Flinku, tylko na custom jakiejś tam implementacji i zrobimy sobie naszą nieśmiertelną szynę danych, po której to wszystko musi płynąć, to właśnie nasze handlerki, które się subskrybują na tą szynę, pełną pełnią rodzaj takich observerów, które dostają ten notyfikacje i dopiero jak je do stanu coś z nimi robią, nie? Jeżeli ta szyna jest odpowiednio zaimplementowana, jest w ścisłym rozumieniu asynchroniczna, czyli nie musimy ciągnąć tych danych stamtąd, tylko właśnie ona nam wpycha. Tak, ten push-based. No to wtedy już mamy to podejście tylko powiedzmy w takim egzotycznym zastosowaniu, ale jesteśmy reactive troszkę. Nie?
Najczęściej takie systemy też są odporne na awarie, bo jak się taka awaria zdarzy, no to cała, cały strumień płynie dalej, nie? Nasz, nasz komponent, powiedzmy, upadł, ale to tylko na chwilę. Panie, czuwam po to, żeby zaraz powstać.
Nie? Jak mawiał Alfred. No i proszę, nie zwiedź Maca. Wstawka. Tak.
Tylko z Batmana. Po co serwisy upadają? Aby powstać.
Dokładnie. Więc one sobie powstają, padają i wszystko jest OK. Bo cała reszta sobie żyje. Nawet, nawet lepiej, bo my mamy taką pętlę odseparowaną od siebie, czyli znowuż mamy ten aspekt z Reactive Manifesto, gdzie jeżeli nasz komponent już całkiem upadnie, że już nawet mu Alfred nie pomoże, no to jest jakiś, no, może wtedy Alfred postawi nowy.
Wszystkie rzeczy, które ten ominął tam wtedy się go naprawi. Wiadomo, tamtego albo do kosza i jedziemy dalej, nie? Bo to jest, to jest tak zwany temporal decoupling, czyli właśnie nie mamy tak. To jest zależności czasowej. Tak naprawdę nieważne, kiedy komponent działa czy nie działa. Ważne, że dane, które on ma obrobić są zawsze dostępne.
Więc wtedy i aspekt filozoficzny był poruszyliśmy.
Filozoficzny, ale to już mówiliśmy: „Wszystko płynie”. Filozofowie starożytni: „Dwa razy wejść do tego samego strumienia”. Ale u nas można. U nas on upadł i my drugi raz wchodzimy. Nie? Wiedzieli. Nie wiedzieli. Te rzeki nie były w starożytności reaktywne. Nie były.
Nie były. Także, także to są, to są te problemy i to są te plusy, plusy i minusy, które to podejście daje. Plusujemy. Natomiast tak. Koniec. Tak. Chyba jeszcze troszkę. To z takich już może luźniejszych obserwacji. No bo anegdota jakaś anegdota, takie spostrzeżenie może. Nie wiem, na ile to się sprawdzi. No bo jakby nie patrzeć, ta nasza tutaj reaktywność, o której wspominamy ogólnie, dość mocno się opiera na, mocno opiera się na takiej współbieżności i na modelu też współbieżności. W sensie, możemy sobie zrobić fajnie reaktywnie w rozumieniu takim observable, czyli takim prawdziwie reaktywnym asynchronicznym procesowaniu naszych eventów i rzeczywiście zrobić opakowania naszego źródła danych, zrobić naszego listenera, wszystkie mapowania i całą tutaj jakby tą naszą dekorację tego, tego naszego asynchronicznego podejścia.
Przyszłość reaktywności w Javie: Project Loom i lekkie wątki
Ale zasadnicze pytanie, dlaczego to robimy? Jest jakby powód, dlaczego to robimy, jest to, że nie chcemy blokować, czyli chcemy maksymalnie wykorzystać zasoby sprzętowe. Teoretycznie maksymalnie wykorzystać zasoby sprzętowe możemy też poprzez użycie wątków, zakładając oczywiście, że wątki i się nie blokują.
Się nie blokują. No, wątki, nie mówimy oczywiście o deadlockach. Zakładamy, że nie mamy tu żadnych deadlocków. Takie klasyczne mamy klasyczne wątki, normalne wątki, tylko że te wątki musiały być w miarę szybkie, w miarę lekkie, prosto do stworzenia w niektórych językach są.
Lub w bibliotekach.
Lub bibliotekach. Ogólnie bardziej właśnie też w językach właśnie w Go i w Kotlinie, czyli goroutines i coroutines. I tutaj tak zmierzam dość płynnie też do czegoś, co zostało poruszone w jednym z odcinków. No bo w Javie, jak wiemy, te wątki nie są zbyt lekkie. Wątki są spięte z wątkami platformowymi, systemowymi. Zrobienie wątku zajmuje dużo czasu procesora, zabiera spory kawałek pamięci na rezerwację stacku tego wątku, najczęściej z nadmiarem. No bo nie wiemy, ile tam będzie danych. Po to też mamy w Javie pulę wątków. No bo te wątki są na tyle ciężkie, że musimy je pulować, żeby prościej z tych puli je brać. Natomiast na horyzoncie jest coś takiego jak Project Loom.
Czyli było o nim. Było o nim. Kto pamięta to to to to ten wie. Pozdro dla kumatych.
Pozdro dla kumatych. I Project Loom ma za zadanie właśnie sprawić, że te wątki będą lekkie. Będziemy mogli sobie tworzyć na poczekaniu pełno, tysiące, dziesiątki tysięcy. I w takim rozumieniu teoretycznie może to nam zastąpić pewien element tej reaktywności. No bo jeśli w każdym z tych wątków da nam narzędzie, da nam narzędzie. Przynajmniej sam Project Loom nie da nam oczywiście tego narzędzia w postaci takiego ładnego, jakby, opisu takiego flow tych naszych eventów, tak jak mamy w przypadku observable, gdzie mamy sobie, mamy sobie pipe i wrzucamy tam operatory w tym pipe’ie i widzimy sobie, jak wszystkie tam się układają i która operacja jest za którą. Natomiast jeśli chodzi o takie techniczne wykorzystanie zasobów i właśnie asynchroniczność i nieblokowanie, to to wydaje się naprawdę super rozwiązaniem. I gdzieniegdzie da się zauważyć pewne, może nie takie wieszczenie, że, że cała te reaktywność tutaj po stronie Javy troszkę odejdzie w zapomnienie. No bo to jeszcze pewnie za wcześnie, bo sam Project Loom jeszcze nie pojawił się. Że jak się pojawi to pewnie ze dwa, trzy lata miną, zanim rzeczywiście.
Hejtują. Bardziej, bardziej chodziło o to, że aż się pojawią jakieś patterny. Później pewnie jeszcze kolejny czas, aż się pojawią wzorce, które nam pomogą właśnie w tej aktywności z użyciem właśnie Project Loom i ogólnie właśnie nowych lekkich wątków. Więc to nie jest nic, co się pewnie wydarzy bardzo szybko, ale rzeczywiście, no, ma to pewien potencjał, żeby zapewnić tą reaktywność przynajmniej na takim technicznym poziomie. No bo teraz bez tak naprawdę jakieś zewnętrznych bibliotek troszkę ciężko jest to ten taki reaktywny flow opisać po prostu w czystej Javie. No bo jest to troszkę i skomplikowane w opisie właśnie, jak te callbacki czy Future i skomplikowane w po prostu trzeba dużo kodu naklepać, żeby sensownie to jakoś obsłużyć.
Nie? I to i to jeszcze z takimi trudnymi wzorcami. Bo, bo właśnie, jak jest potencjał do błędów, na pewno jest potencjał do błędów. No bo tam trzeba synchronizować tą współbieżność, która tu jest. Nie? Ona wynika z asynchroniczności. No i to są trudne zagadnienia. Tak. I nie sami wymyślimy. To są zwłaszcza się to jest przetestować. Nie? Jak to przetestować. No to jest współbieżność i skalowalność i wielowątkowość. No, są tematy trudne i rzeczywiście pisać czegoś na własną rękę nie polecam.
Nie polecam. Nie polecam. Więc, no, tutaj na horyzoncie na pewno coś ciekawego się pojawi z tymi nowymi wątkami w Javie, bo są też fajne zmiany właśnie współbieżności i uproszczenia też w pewnych elementów. To też może wspomóc właśnie ogólnie właśnie praca z wątkami, nawet tymi, tymi lekkimi. Więc myślę, że to jest kwestia czasu, aż pojawi się jakaś fajna biblioteka do takiego reaktywnego programowania właśnie oparta na nowym już podejściu. I może wtedy to będzie naprawdę już moment, kiedy już trzeba będzie to użyć.
Dokładnie. Bo to jest jak w grach, nie? I driverach do grafiki na przykład. Wychodzi nowy feature jakiś, no to musimy poczekać, aż producenci raz, że to nam zaszyją w sprzęcie, a dwa, kiedy się pojawi driver odpowiednie do tego, żeby tego właściwie użyć. Nie? To tak samo tutaj. Pojawia się nowy cacko. Na razie jest w fazie eksperymentalnej. Musimy poczekać, aż troszkę dojrzeje. I wtedy zobaczymy, może coś z tego fajnego będzie.
Aż pojawi się pierwsza odpowiedź na Stack Overflow, jak to rozwiązać.
Ktoś to zrobi. Tak. Najpierw będzie mało, a później i nie będzie zawsze, jak to zwykle bywa, odpowiedzi na akurat nasz problem. No, tak, tak ma. No, ale jak już jesteśmy przy grach, co nie?
Reaktywność w grach i obserwacje końcowe
To jak już przy grach jesteśmy, to warto może nadmienić, że w grach to, to dopiero jest reaktywnie, zwłaszcza w tych takich wymagających współczesnych, nie? Bo tamto o ile tutaj w takich klasycznych jakiś naszych prostych łobówkach dla biznesu, w których tam się stosunkowo niewiele dzieje, to faktycznie tych strumieni nie ma za nadto. Nie da. Przynajmniej, no, jest takie message-driven tylko takie bardzo stonowane, umiarkowany i bardziej do sterowania właśnie za pomocą komunikatów niż do jakiejś reaktywności samej w sobie. No, to wyobraźmy sobie teraz taki świat gry bardzo dynamicznej, lub średnio, ale duży, w którym jest dużo tych źródeł sygnałów. Nie? I one muszą spływać, muszą być analizowane, żeby to płynnie działało. Także tam to się musi dziać i pewnie się dzieje. Nie? Bo, bo to właśnie te pętle gry to to też mają takie zalążki tej reaktywności. Właśnie nawet opowiadaliśmy o tym odcinkach poprzednich, kiedy, kiedy tam analizowaliśmy algorytmy naprawdę porządnej pętli gry, która musi dzielić sobie czas przetwarzania na poszczególne aspekty. Nie? Czyli tutaj po prostu, no, te zdarzenia muszą być dostarczane do odpowiednich słuchaczy w odpowiednich ilościach i w odpowiednim czasie. Zresztą już sami nawet takie tworzyliśmy nie raz gdzieś tam w game objectach Unity jakieś listeners tam reagujące na pewno.
Coś, nie coś o tym wiemy. No, widać, że tutaj jest też pole do zastosowań na pewno tego. No, to nie tak skomplikowane systemy z wielu, z wielką ilością różnych inputów, które tak naprawdę dzieją się jednocześnie i wpływają dość mocno na siebie nawzajem. O, i właśnie jeszcze aspekt jednoczesności. Bo też już podkreślaliśmy kilka razy, że taka nawet równoległość wątków to jest dość często pseudo równoległość, bo to jest wszystko multipleksowane na przykład na tym samym rdzeniu procesora. Jeżeli mamy procesor wielordzeniowy, to tak naprawdę równoległości mamy tyle, ile mamy w sprzęcie, czyli tyle rdzeni, ile nam procesor da. Na każdym z nich odpalony jeden wątek, one się faktycznie dzieją równolegle. Natomiast jeżeli mamy na tym samym rdzeniu, to ten sam wątek musi być pocięty na kawałeczki. I teraz im bardziej go potniemy na kawałeczki, tym bardziej mamy tą responsywność możliwą do zagwarantowania. No bo mamy krótsze czasy odpowiedzi, więc częściej nam się będzie coś działo. Nie? Czyli tutaj częstotliwość, częstotliwość w ogóle przetwarzania takiego strumienia, czy też nawet produkowanie tego strumienia, przy produkowaniu to jeszcze jest inna kwestia. Ale w ogóle częstotliwość umożliwiania wpięcia się w te poszczególne update’y, nie? To też tutaj bardzo mocno wpływa na tą, no, właściwie na wiele cech z tego naszego Manifestu reaktywnego.
Tak, tak. Bo to już dużo jeszcze rzeczy, które pominęliśmy, właśnie. Jak wspomniałeś o wpięciu się w strumień, jakby obciążeniu, no to koncepcję backpressure, czyli możliwości właśnie sterowania obciążeniem z punktu widzenia subskrybenta. To jeszcze dużo możemy. Możemy jeszcze ponawianie po błędzie w ogóle też pominęliśmy. To też jest coś takiego, co to są wszystko. Komplikuje.
Komplikuje. To są właśnie cechy tej, tej reaktywności i dopiero, dopiero wtedy możemy te elementy wykorzystać, a nie możemy tego na przykład zrobić w takich zwykłych strumieniach javowych, czy w jakichś naszych. No, tamtego nie ma po prostu. Tamtego nie ma. To jest po prostu inna,
Inna troszkę zestaw.
Dokładnie. Dokładnie
Czyli co, ogólnie fajne ten RX?
No, fajne. Fajne, fajne. Można coś tam pokodować. Można poużywać.
Podebugować.
Podebugować. Mimo że to jest trudne. Także polecamy, ale trzeba sobie też jasno powiedzieć, że no, może być, może być na pierwszy rzut oka może być…
…ciężko.
Ciężko. Może być ciężko. Trzeba się, no, troszkę przestawić. Trzeba przestawić kolegów i trzeba sobie trochę ten model mentalny troszkę inaczej ułożyć.
I najlepiej ze dwa tutoriale przeczytać.
A jeszcze lepiej mieć powody, żeby to użyć, bo używać tylko po to, żeby użyć, może się to skończyć nie najlepiej.
Nie inaczej. No to co, to chyba na dzisiaj by było chyba tyle. To dzięki, Wojtek.
Dzięki, Michał.