Už před pár měsíci jsem si tento nadpis přidal mezi rozpracované koncepty tohoto blogu, ale záhy jsem si uvědomil, že na tuto otázku ještě nedokážu zcela uspokojivě odpovědět. Vzpomínám si, když jsem na Devoxxu v Krakově viděl RxJavu poprvé, krom toho, že mi nebylo úplně jasné, jak to přesně funguje, jsem si kladl jednu a tu samou otázku: “K čemu je to sakra dobré?”. Není to to samé, co už umí Streamy v Javě 8? Čas i zkušenosti pokročily a tak nastala chvíle podělit se o to, jak vidím RxJavu já.

Demystifikace

Asi první věc, která vás při pohledu na RxJavu trkne do očí je, že to vypadá hodně podobně jako javovské Stream API, ba co víc, možná vám bude připadat, že to i vlastně dělá úplně to samé. Dále si všimnete, že má RxJava mnohem více operátorů (metod), takže vám už nic nebráni k tomu, abyste došli k závěru, že RxJava jsou vlastně takové vylepšené Streamy. Pojďme si v této otázce udělat jednou provždy jasno 🙂 .

Může být RxJava použita ke stejnému účelu jako Stream API? Ano, může, ale zrovna k tomuto účelu stvořena nebyla a nedoporučoval bych to. Proč? Zkráceně řečeno, asi by to byl overkill a krom toho, Streamy by byly určitě rychlejší. O co tedy přesně jde? Aby se daly Streamy trochu blíže přirovnat k RxJavě, zkusme si způsob, jakým pracují, představit jako nějaký přenos.

Od Stream API k RxJavě

Řekněme, že budeme pomocí Streamů mapovat list čísel na list Stringů. V našem pojetí se tedy bude jednat o přenos něčeho, co je v paměti, do něčeho jiného, co je taky v paměti. První poznatek tedy zní:

Pokud provádíte operaci, která se odehrává pouze v paměti, vždy použijete Streamy.

Výhodou operací v paměti je, že snad nikdy neselžou (leda snad, že by vám odešla RAMka). Nyní si představme, že zdrojem je třeba HDD a cílem opět paměť (např. načítání souborů). Řekněme, že jakmile opustíme oblast paměti a pracujeme s nějakým externím zařízením, pravděpodobnost nějaké chyby během přenosu o něco vzroste, nicméně s HDD taky až tolik problémů nebývá a tak přejděme rovnou k tomu, že přenášíme něco z paměti přes síť do databáze, která je na druhém konci světa. Tady už je zřejmě jasné, kam tím mířím, a tedy druhý poznatek zní:

Pokud přenáším data z nestabilního zdroje nebo na nestabilní cíl, kde by k úspěšnému přenosu pomohla re-try logika, již zde můžu zvážit použití RxJavy.

Komplexita narůstá

Prozatím jsme se bavili jen o jednom zdroji a jednom cíli. Co když mám zdrojů a cílů více? Co když některé zdroje/cíle jsou asynchronní a běží v různých threadech? V různých thread poolech? Co když mi některé ze zdrojů poskytují data v dávkách a potřebuju je rozbít na menší části? Co když, některé ze zdrojů nebo cílů nestíhají data zpracovávat? Chci v takovém případě blokovat celý přenos, anebo data zahazovat (např. při realtime zpracování)? Zde a právě zde vám nyní již jistě dochází, že Streamy jsou totálně ze hry a že jedinou správnou odpovědí je RxJava 🙂 .

A zase klesá …

Výše jsem nastínil jeden úhel pohledu, který snad již vnesl dostatek světla do otázky, k čemu je RxJava dobrá a zřetelně její use-case odlišil od toho, k čemu jsou naopak dobré Streamy. Abychom pochopení RxJavy ještě více prohloubili, měl bych tu ještě pohled druhý a proto začněme zase od začátku. Bude se to obecně týkat problematiky slovíček synchronní/asynchronní. Vraťme se opět k našim zdrojům a cílům. Ať už je cíl synchronní nebo asynchronní, z programátorského hlediska je mi to úplně fuk, protože v obou případech se v kódu jedná (tak nebo tak) o jediné zavolání metody. Jak je to se zdrojem? Pokud je synchronní, jo, v pohodě, prostě nějaký foreach a je vystaráno. Ale co když je zdroj asynchronní? Je to problém? Tady nastává jistá rozporuplnost, protože odpověď je ano i ne, a to v závislosti na použití 🙂 . Ještě než si vše vysvětlíme na konkrétním příkladu, dovolil bych si malou vsuvku:

Reaktivní programování

Buzzword, který (minimálně před rokem) docela zacloumal nejednou programátorskou komunitou. Ať už toto slovo milujete nebo nenávidíte, je mezi námi a je třeba si na něj zvyknout. Jednou jsem dokonce narazil na jeden jízlivý tweet od Maria Fusca, který si stěžoval, proč vůbec tento termín v nedávné době vznikl, když už tu s námi reaktivní programováni je snad již od samého počátku vývoje programovacích jazyků. Ano, to je sice pravda, ale můj názor je takový, že tento termín smysl má právě proto, že až teprve nyní máme k dispozici skutečně sofistikované nástroje ke zvládnutí relativně komplexních úloh v dané oblasti. Nemluvě o vzniku určitých myšlenkových směrů o “reaktivním” způsobu, jak designovat celkový tok dat v naších aplikacích.

Co to reaktivní programování vlastně je? Na to mám jediné slovo: callback. Věřím (a doufám), že většina z vás už někde nějaký ten callback použila, čímž vám chci pogratulovat: ano, jste reaktivní! 🙂 Výhody tohoto přístupu jsou, že nic neblokujete a daný objekt vám zavoláním vaší callback funkce dá sám vědět, až vznikne nějaká událost. V reálném životě byste taky přeci neusilovali o dívku stylem, že jí budete každých 5 minut volat a ptát se “Ahoj, už mě chceš?”, ale místo toho jí jako správný reaktivní balič hrubáckým hlasem řeknete: “Hej bejby, tady je moje číslo a až mě budeš chtít, tak zavolej”. Což by asi ve skutečném životě nikdy nezafungovalo, ale to už jsem trochu odbočil … Každopádně když vám někdo řekne, že programuje reaktivně, nic jiného než publish-subscribe model za tím opravdu není.

Zpět k příkladu

Řekněme, že chcete přenášet data z jedné databáze do druhé. Začněme tím nejhloupějším způsobem, a sice že zdrojovou databázi pojmete synchronně a zavoláte na ní metodu, která vykoná nějaký SELECT a vrátí vám List řádků (nebo použijme raději konverzi na jejich objektovou reprezentaci, tedy tzv. beanů). Nyní již stačí celý List projít foreach cyklem (chraň vás ruka Páně od použití přístupu přes index) a jeden po druhém ho nasypete do databáze cílové, že? To vše je moc pěkné až do chvíle, kdy vám metoda ze zdrojové databáze vrátí příliš velký List na to, aby se vám vešel do paměti a celá aplikace vám zhavaruje. Dobře, teď už budete chytřejší a rozhodnete se zdroj pojmout asynchronně, tzn. budete “streamovat”. Po API vašeho DB konektoru budete chtít metodu, která vám umožní předat mu funkci, která se zavolá pokaždé, když se ze zdrojové databáze načte 1 nový řádek. Pokud vám vaše API nic takového neumožňuje, doporučuji použít stroj času a cestovat do roku 2017 🙂 . V onom callbacku nebude nic jiného než vložení právě příchozího beanu do cílové databáze, velmi jednoduché. Tím jste vyřešili problém s pamětí a pozor, již od této chvíle programujete reaktivně a zároveň RxJavu vůbec nepotřebujete!

Ovšem objeví se další problém. Přenos je příliš pomalý, protože do cílové databáze vkládáte beany po jednom a to je neefektivní. To, co nyní chcete je, aby se beany načítaly ze zdrojové databáze streamovaně (asynchronně), ukládaly se do nějakého dočasného Listu, a jakmile tento List dosáhne stanovené velikosti, celý ho dávkou přenést do databáze cílové a zároveň pokud na zdroji již nejsou žádné další data, přenést nasbírané data v aktuálním listu i přesto, že ještě požadované velikosti nedosáhl. Toto je kompletní popis tzv. “dávkové logiky” (nebo “batchové”), která jakkoliv se jeví jednoduše, věřte mi, může být docela záludná neboť lidský faktor dokáže udělat divy a toho se nikdy nezbavíte 🙂 . Je to právě tento zlomový moment, kdy začíná mít smysl zvážit použití RxJavy. Proč? Protože pokud si nyní obalíte asynchronní načítání z vašeho DB konektoru do své třídy dědící z Flowable, stačí vám zavolat metodu .batch(…) a tuto logiku dostanete úplně zadarmo a navíc se zárukou, že je to napsáno skutečně správně.

Tímto bych chtěl vyvrátit domněnku, že RxJava slouží pouze k realtime asynchronním přenosům, ale jak je vidět, může být užitečná i v těchto relativně jednoduchých, avšak záludných use-casech. Pokud vám to stále nestačí, mám pro vás ještě 2 skutečné use-casy, ve kterých mi RxJava přišla vhod.

Sampling

Představte si, že píšete desktopovou JavaFX aplikaci a máte v ní textový vstup. Uživatel do tohoto vstupu bude zadávat nějaký údaj a vy chcete, aby se tento údaj v reálném čase ukládal ať už někde na HDD nebo do databáze, protože kdo se chce v dnešní době obtěžovat klikáním na tlačítko Save, žejo? 🙂 Problém je, že pokud je uživatel zdatný a napíše třeba 20 znaků za vteřinu, onChange událost vygeneruje taktéž 20 událostí za vteřinu, což je pro databázi zbytečné zatížení. To, co chcete, je, aby se údaj v databázi updatoval při každém stisku klávesy, ale např. ne častěji, než jednou za půl vteřiny. Ruku na srdce, chtěl by tuto logiku někdo z vás implementovat ručně? Navíc kdyby věděl, že s RxJavou je to jen jediný řádek kódu navíc? 🙂

Přepínání vláken

Krom buzzu ohledně reaktivního programování jste určitě také mohli zaregistrovat, že se o RxJavě taky hodně mluví v souvislosti s vývojem aplikací pro Android. Můj další příklad se opět bude týkat JavyFX, ale věřím, že tato problematika bude u Androidu podobná. Řekněme, že máte nějaké UI, uživatel klikne na tlačítko a vy potřebujete vykonat nějaký delší výpočet. Samozřejmě nechcete, aby UI zamrzlo, a tak daný výpočet spustíte v novém threadu. Až je vše dopočítáno, chcete výsledkem updatovat některou z UI komponent a ejhle … dostanete exception, že UI komponenty lze updatovat pouze z určitého vlákna, které je pro tento účel určené. Přiznávám, že tohle by nemuselo být až tak těžké vyřešit i bez RxJavy, ale opět, kdo by odolal jednoduchému .observeOn(…) ? 🙂

Závěrem

Určitě se toho s RxJavou dá udělat mnohem více, ale to je již opravdu vše, co ze sebe v tuto chvíli dokážu vymáčknout. Snad byl pro vás článek přínosem a motivací, jak a kde RxJavu ve vaší code-base uplatnit. Vyhněte se však všem reaktivním úchylům, kteří vás budou nabádat k přepsání celého vaše API do Flowablů, Singlů či Maybe-jů, což považuji za zcela zvrácené. Je jediná programátorská poučka, která nikdy nepřestane platit: “Přemýšlejte”. Přemýšlejte a mějte čistý kód 🙂 (a) abyste nepřišli zkrátka, pokusím se na otázku v nadpisu článku odpovědět takto:

“RxJava je knihovna, která slouží k pokročilé kontrole nad asynchronními toky dat.”