Чому вам НЕ потрібен ReactiveX

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті

Багато «нових ідей» сьогодні в програмуванні (та й не тільки) висловлені далеко не вперше. Але той факт, що ідея згадується в статті десь років 30-40 тому, а то й давніше, не гарантує її популярність.

ReactiveX — це бібліотека (набір бібліотек), що винесла ідею «реактивного» підходу у програмуванні на новий рівень і зробила його популярним (RxJava має 47К зірок на Github, 188-е місце в топі).

У мене була нагода використовувати цю бібліотеку ще з перших версій RxJava в різних варіантах: в Android застосунках, desktop проєктах та в серверних Java-демонах. Дещо працює з ReactiveX і до сьогодні, щось встигли переписати без використання цієї бібліотеки, а часто все від початку було написано без неї. З огляду на такий досвід і написана ця стаття.

Звідки взявся ReactiveX

Вікіпедія пише, що ReactiveX виник як побічний продукт якогось проєкту в Microsoft, про який ніхто не знає. Але це насправді нікому не цікаво. 🙂

Корисніше буде зрозуміти, що спонукало шукати новий підхід. У тому вигляді, у якому ця бібліотека є зараз, вона покриває цілий спектр задач, про які є сенс згадати.

Підписка на події. Стара як світ задача: на екрані є кнопка, потрібно щось зробити, коли хтось натиснув на неї. Як це вирішують:

  • Найпростіше: зробити поле onClick, що містить посилання на callback.
  • Інколи цього не достатньо, і ду-у-уже бажано, аби була змога підписатися на подію декілька разів. Тоді роблять обгортки на кшталт EventListener — класу, що інкапсулює посилання на список callback-ів. Зазвичай у UI-фреймворках на цьому й зупиняються, оскільки щось складніше починає бити по performance.
  • Іноді до попереднього хочеться додати, наприклад, debounce, обробку в іншому потоці, чи викликати ще якусь тривалу операцію, після якої вже зробити щось своє. Тут RX і допоможе.

Застосування як Promise. Існує багато історій про те, як поява Promise в JS полегшила роботу та допомогла уникнути callback hell. В цілому, Promise реалізує частину того, що доступно в ReactiveX. Тому, з появою останнього, promise-based-підхід став доступний в більшості мов програмування. Цікаво, що promise-и з нами орієнтовно лише з 2014. А в RxJava перші релізи вийшли у 2013. Можна сказати (з натяжкою), що ці інструменти — однолітки.

Застосування як Stream. Тут я маю на увазі як Java Stream, хоч і подібні ідеї присутні багато де: зробити ітератор, перетворити його, згорнути результат куди треба. У певному сенсі ReactiveX теж так може.

Побудова стабільних pipeline-ів. Наприклад, у вас є потік подій (запити, логи, транзакції), і ви хочете зробити демон, який їх приймає, обробляє (паралельно в різних потоках) і складає в базу (батчами, щоб якось ефективно було). Це все можна реалізувати на чергах, але в термінах ReactiveX, цей демон може мати вигляд одного pipeline.

Зрештою якщо об’єднати ці всі ідеї та додати до них купу цікавих операторів, вийде ReactiveX.

Як працює ReactiveX

Тут і далі приклади будуть на RxJava — оскільки саме із цією версією ReactiveX мені доводилось працювати найбільше.

Розглянемо код

Observable<Integer> A = Observable.range(0, 30)
    .skip(10)
    .take(5)
    .map((v) -> v+100);

A.subscribe((v) -> System.out.println("A"+v));
A.subscribe((v) -> System.out.println("B"+v));

Тут ми можемо спостерігати одразу декілька цікавих аспектів:

  1. Доки ми не зробимо subscribe, як такого pipeline-у обробки подій чи даних не існуватиме. До цього моменту — то лише декларації. Тому тут два subscribe-и виведуть дані двічі (більш зрозуміло стає, якщо розібратись як працює Observable.create).
  2. Також бачимо, як ми можемо перетворювати «потік даних» від Observable до subscribe, застосовуючи декілька з численних готових операторів (skip, take, map).
  3. Є сенс додатково зауважити, що оскільки кожен subscribe запускає окремий потік обробки, всі ці оператори роблять свою роботу теж двічі.

В цілому, якщо не вдаватись у деталі, ReactiveX будує передачу даних на push-based принципі (подивіться інтерфейс Observer.java): тобто дані передаються між ланками шляхом виклику onNext.

Тобто:

  • ви будуєте pipeline;
  • у нього викликаєте subscribe;
  • цей subscribe по всьому ланцюжку доходить до початкового Observable (їх може бути декілька);
  • в Observable викликається subscribe, який починає onNext-ити події;
  • ці події проходять операторами;
  • й осідають в Consumer-і subscribe-а.

Кожен потік даних ініційований subscribe-ом складається з послідовності onNext-ів та в кінці одним onError або onComplete. Тут важливо розуміти, що якщо десь виникає необроблена помилка і створюється onError, обробка даних зупиняється і залишок даних ігнорується. Ця поведінка повністю збігається з логікою роботи promise-ів. Але не очевидна, якщо потрібен, наприклад, EventHandler (зупинимось на цьому пізніше).

Якщо ви раніше не користувались RxJava, для повноцінного розуміння потрібно також розібратись з Observable vs Flowable, різними способами їх створення, поняттям backpressure, з операторами subscribeOn / observeOn та подивитись бібліотеку операторів (там багато гарних діаграм 🙂). Але опис цих речей виходить за рамки статті, краще глянути документацію.

Типові способи використання

Використання в ролі EventHandler. Типовим способом опису джерела подій є PublishSubject наступним способом:

// створюємо “реєстр” обробників
PublishSubject<String> onEvent= PublishSubject.create();

// підписуємось на події
onEvent.subscribe((v) -> System.out.println("A: "+v)); 

// ініціюємо подію
onEvent.onNext("1");
onEvent.onNext("2");

//підписуємось на подію, але ігноруємо більше однієї події за секунду
onEvent.debounce(1, TimeUnit.SECONDS).subscribe((v) -> System.out.println("B: "+v));
onEvent.onNext("3");
onEvent.onNext("4");

На цей onEvent можна підписатись багато разів, з різними операторами у дорозі.

Застосування як Promise. Типовий спосіб використання виглядає як:

service.apiCall()
  .flatMap(value -> service.anotherApiCall(value))
  .flatMap(next -> service.finalCall(next))
  .subscribe()

Тут ми експлуатуємо flatMap так, щоб Observable, які повертаються з service.*, оброблялись почергово. Тобто три виклики будуть оброблятися послідовно, хоч вони й мають асинхронну природу.

Застосування як Stream. Для такого сценарію в документації є приклад:

Flowable.range(1, 10)
  .parallel()
  .runOn(Schedulers.computation())
  .map(v -> v * v)
  .sequential()
  .blockingSubscribe(System.out::println);

Тут ми паралельно обробляємо 10 задач на окремому Scheduler (інкапсулює Thread Pool), а після обробки складаємо їх знову в одну послідовність.

Типові проблеми

Неочікуваний onError. Якщо використовувати сценарій з EventHandler-ом і підписуватись на Subject, і з якоїсь причини обробник події викидає помилку, підписка скасовується. Ну а зловити NPE десь посеред onClick — то звичайна справа. Погодьтесь, що відписати обробника події як реакцію на помилку — то занадто. Тут можна запропонувати використати retry-оператор, але очевидності до роботи це не додає.

Лeгко зловити OutOfMemory. Найпростіший приклад може виглядати так:

Observable.range(1, 1000000000)
  .toFlowable(BackpressureStrategy.BUFFER)
  .blockingSubscribe(x -> {
     Thread.sleep(1000);
     System.out.println(x);
   });

Цей код виїсть усю доступну пам’ять і вивалиться в OutOfMemory. Чому? А все тому, що отой toFlowable створює буфер необмеженого розміру. І це є стандартна поведінка для багатьох операторів. За своєю природою буфери в реактивному підході — це перша необхідність. І їх використовується багато. Але ось чому вони тут за замовчуванням unbounded — це загадка. Якщо вам здається, що у вас достатньо досвіду, щоб такого уникати — вас точно десь очікує «сплячий» OutOfMemory.

Автори бібліотеки могли б легко нівелювати подібну проблему, якби всі без винятку буфери за замовчуванням були б поставлені, наприклад, розміром в 100 елементів, але з певних причин вибір був зроблений на користь того, аби залишити їх необмеженими. Для цієї дискусії навіть тікет є.

Відсутність інструментів відлагоджування та моніторингу. Якщо під час робити десь вилітає exception, то stack trace, скоріш за все, буде містити велику кількість сміття, водночас корисних викликів може й не виявитись (залежить від операторів, які використовуються). Це можна вважати проблемою, але варто зазначити, що цей нюанс є практично у всіх подібних інструментах. Навіть з корутинами не завжди все в порядку: почитайте за Kotlin.

З приводу ж моніторингу, питання може стати руба, якщо порядок обробки даних є одним з центральних процесів вашої програми. Між етапами обробки будуть черги, і ви захочете знати, де ж у вас вузьке місце: чи файлова система, чи мережа, чи cpu, чи десь щось погано написано. Й інформація про те, в якому місці пайплайну у вас застряють дані, дуже допомагає для пошуку проблеми. І ви захочете це експортувати кудись в Prometheus і потім в Grafana. У RxJava налагодити моніторинг — місія майже неможлива.

Неочікувана поведінка операторів. Тут, звісно, твердження суб’єктивне, адже в документації написано, що і як працює. Але все-таки розгляньмо наступний випадок.

Часто виникає задача, коли є потік подій, які треба масово записати в базу. Щоб це робити ефективно, їх треба писати в базу batch-ами. Для такого групування є оператор buffer. Наче добре, але ... цей буфер може або віддавати групи по count елементів (не підходить, оскільки незаповнений буфер буде вічно сидіти в пам’яті), або кожний фіксований інтервал часу (не підходить, адже потенційно недоутилізує всі можливості бази). А треба, щоб він віддавав пачки не більше ніж count, але віддавав батч одразу, коли підписник готовий писати наступну пачку в базу (без жодної затримки, якщо в черзі є задачі).

Можна було б ще наводити приклади операторів, що «чогось не вміють». Але, з іншої сторони, неможливо реалізувати всі-всі-всі варіанти в бібліотеці.

Надзвичайна складність реалізації своїх операторів. Як продовження попереднього. Наче ж просто: якщо в бібліотеці чогось не вистачає — просто візьми та допиши. Насправді свого часу я так і зробив. Зрештою в мене є декілька операторів під свої потреби, правда, ще під старі версії RxJava.

Процес написання свого оператора виглядає десь так: спочатку шукаєш найбільш схожий за структурою готовий оператор. Потім довгий час намагаєшся розібратись, як він працює. Опісля копіюєш і правиш під себе. У кінці приходить розуміння, що впевненості в тому, що нічого не провтикав, немає зовсім. І справа навіть не в тому, заплутаний код чи ні, а в тому, чи вдалося повністю дотриматись досить неочевидних контрактів. Унаслідок цього пишеться купа тестів і приймається на віру, що воно не зламається.

Якщо хочете, можете взяти два-три цікавих вам оператори й спробувати розібратись, як вони працюють.

Альтернативи

Шукати єдину альтернативу не варто, оскільки однією з причин проблем з ReactiveX є те, що автори спробували зробити все й одразу.

Але для специфічних задач є не гірші, а то й кращі рішення.

Для EventHandler використовуйте те, що є у вашому UI-фреймворку. У більшості випадків цього достатньо. А замість debounce / throttle ставте кнопці disable, поки ви не хочете повторних натискань. Якщо ж у вас «свій фреймворк», і там треба події — не полініться і напишіть свій EventHandler клас на 40 стрічок. Усі ми їздимо на своїх велосипедах — це нормально.

Для Promise ... краще взагалі не використовуйте Promise — це напівміра і, як на мене, антипаттерн. Замість нього подивіться на корутини. Вони вже зʼявляються майже всюди. Це зробить ваш код чистішим та простішим. На Java, якщо дуже треба, напишіть частину на Kotlin — пограєтесь з компіляцією, але буде краще, ніж з RX чи CompletableFuture.

Для Stream-like задач в Java використовуйте Stream, а в інших мовах шукайте схожі механізми. Функціональне програмування зараз люблять і такі можливості з’являються.

Для багатопотокових pipeline-ів у мене зараз немає гарної рекомендації. Розумію, що рішення на чергах буде громіздким. Але ви будете знати, що воно не зламається через причину, яку треба шукати глибоко в надрах бібліотеки. Існують, також, проекти як Apache Beam, але це тема для окремої дискусії.

Для Android писати код складно. Але Rx простіше не зробить — ви просто поміняєте одні граблі на інші. Хочете простіше — пишіть на Flutter/Dart — кращого підходу для UI я ще не бачив (якщо не розглядати веб — то інше трохи).

Однаково, ReactiveX!

Якщо ж ви все-таки вірите в те, що цей інструмент для вас, не піддавайтесь на зовнішню простоту бібліотеки та кожен раз намагайтесь переконатись, що ви точно розумієте, що відбувається:

  • Що буде, якщо підписатись двічі? Чи subscribe взагалі присутній? (наче смішна помилка, але трапляється часто)
  • А якщо десь помилка? Навіть якщо її «не може бути» (OOM, NPE чи ділення на нуль може бути всюди).
  • Скільки та які потоки запускаються, скільки їх запускається, коли та де вони запускаються (в яких pool-ах)?
  • Як налаштувати моніторинг процесів (якщо такий має сенс)?
  • Чи ви впевнені, що якийсь Map/FlatMap, що виконується в якомусь pool-і, не зависне і не призведе до того, що pool більше не зможе приймати задачі? Така проблема запросто може зʼявитись у зовсім неочікуваному місці.
  • З іншої сторони: чи впевнені ви, що у вас немає безконтрольного створення потоків, що кінець-кінцем призведе до виснаження ресурсів, якраз коли навантаження трохи підросте?
  • Як «тихо» зупинити виконання, якщо прийшов, наприклад, KeyboardInterrupt, і водночас не втратити дані, що перебувають у процесі обробки?

Звісно, схожі проблеми можуть виникнути й без використання ReactiveX, але бібліотека точно не спростить роботу з ними.

Висновки

Однією з проблем бібліотеки є те, що автори намагались реалізувати інструмент як швейцарський ніж, який можна було б застосувати для багатьох принципово різних задач (як приклад, promise, stream та event listener), що диктувало певні архітектурні рішення (як логіка обробки помилок).

З іншої сторони, реалізація вийшла такою, що використання бібліотеки змушує враховувати багато факторів, деякі з яких не те щоб очевидні. А як ви розумієте, програмувати складно, коли не завжди очевидним є розуміння того, що може піти не так.

В цілому, ReactiveX несе дуже гарну ідею, але реалізація в тому вигляді, в якому вона є зараз, навіть якщо у всьому розібратись, спричиняє більше проблем, ніж їх вирішує.

👍ПодобаєтьсяСподобалось5
До обраногоВ обраному4
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Маю значний досвід використання RxJava, і кожен раз, переглядаючи реалізації операторів, я відкриваю для себе щось нове.

Цей фреймворк надає зручний спосіб обробки даних, пропускаючи їх через потрібні оператори. Rx дозволяє лаконічно реалізувати складну бізнес-логіку, таку як послідовність combineLatest + flatMap + replay + zip + debounce, і ефективно керувати потоками за допомогою вбудованих скедулерів (io, single, computation).

Однак одним з великих недоліків є високий поріг входу, пов’язаний з розумінням десятками операторів, контрактів, типів емітерів та їх особливостей. В результаті досить тяжко знайти розробників які розуміють цей фреймворк, це стає критичним, особливо коли проект тісно пов’язаний з використанням Rx.

Насправді, в більшості проектів Rx використовувався для дуже простих задач, таких як виконання операцій на іншому потоці або тривіальна обробка даних, що у 2023 легко реалізувати за допомогою Coroutines + Flows.

RxJava був популярний в той час, коли в Android-розробці ще не було Coroutines чи Flows.

переглядаючи реалізації операторів, я відкриваю для себе щось нове.

Розумію вас ... але кожен раз, коли доводилось це робити, виникала думка: «випилювати це все треба з цього проекту, а то буде біда» :)

легко реалізувати за допомогою Coroutines + Flows.

Якщо я вірно зрозумів, про що ви, то тут Rx дуже погано справлявся. Звісно, закинути задачу в scheduler — це не проблема. Але тут ми натягуєм «послідовне виконання коду» на Rx. А там, де послідовне виконання, дуже швидко треба обробити помилку, дописати if, а то і взагалі цикл — і тут Rx повністю капітулює перед Coroutines.

combineLatest + flatMap + replay + zip + debounce,

Оператори в Rx — прекрасні, хоч і не ідеальні :) Але, як на мене, лише з точки зору ідеї.

Rx дозволяє лаконічно реалізувати складну бізнес-логіку, таку як послідовність combineLatest + flatMap + replay + zip + debounce,

Якщо я вірно зрозумів, це можна віднести до класу «стабільних pipeline-ів» зі статті.
Могли б ви поділитись досвідом що ви робите з помилками? Адже все ж розвалюється при першій ж проблемі.

Так, саме стабільність гнучкість та лаконічність pipeline-iв основна перевага RxJava.
Щодо помилок, то було достатньо того, що пропонує сам Rx з набору операторів, onErrorResumeNext, doOnError, retry, та інше.
Якщо мова про щось не тривіальне, можливо це загальний обробник помилок Rx, RxJavaPlugins.setErrorHandler() який дозволяв додатково обробити помилки і вирішити чи пропагувати їх далі у системі чи можна просто залогувати або проігнорити (можливо певний Exception не є критичним).

RxJavaPlugins.setErrorHandler()

Це обробник останньої надії — глобальне налаштування — і, як наслідок, антипаттерн. Тобто, його ставити і логувати — то необхідно. Але якусь логіку туди дописувати — то дуже «таке собі» рішення.

стабільність гнучкість та лаконічність pipeline-iв основна перевага RxJava.

В нас, напевно, дуже різний досвід ... з мого досвіду, воно НЕ стабільне, НЕ гнучке і НЕ лаконічне :)
Не стабільне — в тому сенсі, що легко попасти в OOM, легко зловити непотрібний unsubscribe при onError, якого «бути не могло».
Не гнучке та не лаконічне — для роботи в режимі promise — виключно лінійний workflow. Будь-яка нелінійна логіка, робить pipeline таким, що складно читати.

Я розумію, що оператори там круті, і, якщо робити pipeline подій, то оператори можуть «зробити магію» — і це виглядає лаконічно — тут згоден з вами. Але ж воно розвалиться на першому ж onError, який ви не чекаєте, а той самий retry розчистить всі буфери і викине дані і спрацює лише на частині pipeline-у. ... не хочу повторювати всі тези з статті :)

-----

Мені здається, в нас зараз дискусія перейшла в те, що кожен з нас суб’єктивно вважає лаконічним, наприклад.

Можливо, ви могли б поділитись своїм досвідом, коли це круто спрацювало? Можливо, у вас є свої підходи до особливостей, згаданих в статті?

На рахунок

наслідок, антипаттерн

, це суб’єктивна думка кожного.

Щодо

легко зловити непотрібний unsubscribe при onError, якого «бути не могло».

та

що легко попасти в OOM

Не пригадую подібних випадків, хоча додатком користувались мільйони людей і це був великий мед.тех з дуже скдадною бізнес логікою і сотнями різних компонентів.

Можливо якість коду, розуміння командою внутрішньої роботи Rx, хороший Code Review, та сотня QA інженерів не дозволяли викатити щось нестабільне у прод.

Можливо, ви могли б поділитись своїм досвідом, коли це круто спрацювало? Можливо, у вас є свої підходи до особливостей, згаданих в статті?

Ми мали реалізовану стейт машину для керування комунікацією з підключеним пристроєм (BLE), яка включала в себе з обробку стейтів підключень, обробку помилок, виконанням різної бізнес логіки для кожного зі стейтів, встановлення комунікаційного каналу, обміном ключами, різні трігери і обмін даними.

Як результат кількість операторів в одному з чейнів могла перевалити за 50, при тому, що вони інколи об’єднувались в більш глобальні через combine, zip...

Це не була хороша практика, дебажити такий код неможливо, проаналізувати та розібратись
також, не говорю уже про Unit Tests.
Якщо виникали рідкісні сайд дефекти, це означало хорошу подород в дебаг сесію на декілька днів...

Насправді в свій час був популярний підхід Event Bus в Android, але його мало хто застосовував, тому що йшли на багато простішим шляхом через колбеки. Той же Event Bus перекочував і в бекенд (Spring цей підхід є і в .Net навіть окремі сутності зробили — event). Просто треба розбиратись у підходах, а у нас як... Просто взяли відео продивились і все. Нема глибоко і продуманого підходу. На простих проєктах — це працює. На великих — потім все приводить до втрати часу (тут попрацював і далі пішов). Це відомі проблеми і добре все описано у Р. Мартіна.
Щодо coroutines — ідея не нова і має імплементацію і в С++, як власне будь які інші підходи, які кочують між мовами і фреймворками.
Як висновок — як би це був поганий підхід, його не намагалися б застосовувати і інших мовах.

Дві речі яуі вирішує ця технологія.
1. Це підхід до ресурсів.
2. Це конкаренсі.
Якщо складно розуміти технололгію, то не треба писати, що вона вам не потрібна.
Я десь вже писав. Але Angular на цьому побудован та SwiftUI на це перейшов. Android в багатьох фреймоворках це підтримує (і Rx і coroutines).
Якщо вам треба щось швидко — то це не про Rx.
І ще раз — це не про швидкість — це про ресурси.
Усі мови цю технологію підтримують і навіть Spring теж має — Reactor.
Ще одна цікава технологія існує, але її теж рідко використовують, це модель актор.
Є дуже багато компаній, які викорустивують Akka і Tesla там теж. Але нікому з їх інженерів не йде в голову писати про те, що ця технологія не потрібна.
Як приклад — вона є базою в Erlang, завдяки цьому продукти мають дуже великий процент стійкості до відмови — 99,99999%.
Все, що маю додати.

Дві речі яуі вирішує ця технологія.

Я не намагаюсь критикувати підхід. Схожі ідеї зараз багато де успішно використовуються. В статті критика лише конкретної реалізації.

1. Це підхід до ресурсів.
2. Це конкаренсі.

Не впевнений, що ви маєте на увазі у випадку ресурсів, адже RX ними не керує ... хіба за потоки мова, але про це ви написали другий пункт :)
В другому ж, ... якщо я вас вірно зрозумів ... то корутини в kotlin вирішують, практично, все, що тут можна зробити, ... навіть краще. Якщо масова обробка, то overhead самого rx буде суттєвий (бачив випадки, коли overhead ArrayBlockingQueue вже суттєвий).

Spring теж має — Reactor

Не користувався цим ... бачу, що в основі Reactive Streams — конденсована специфікація на основі ReactiveX. Скоріше за все, там успадковані ті ж граблі.

У Java світі, коли не було ні kotlin coroutine, ні project loom, цей Reactive Streams виглядав значно притомніше (адже альтернативи не було). Станом на сьгодні ж — то переускладнено все і не ефективне (ні по часу на розробку, ні по використанню ресурсів).

Доречі, про сам підхід ... в певному сенсі, Netty був зроблений на схожому (з натяжкою) підході, і працює там все просто казково. Вони почали ще раніше — з 2004го: про асинхронність (у вузькому сенсі) тоді ще мало хто чув (хіба, автори epool), а Netty вже використовували ці ідеї. Використали б вони RX (хоча його тоді ще не існувало) — нічого доброго не вийшло б.

Не впевнений, що ви маєте на увазі у випадку ресурсів, адже RX ними не керує ... хіба за потоки мова, але про це ви написали другий пункт :)

Як раз керує через підхід обсервера — юзер підписався і пішов далі займатися своїми справами. Немає блокуючого виклику.
Стосовно

kotlin coroutine,

це якщо було прийнято рішення писати бек на Kotlin, то це має сенс. Є як завжди свої pros і cons. Основний недолік, все ж таки Kotlin не завжди підтримує останню JVM.
Для Android — то тут вже інше питання, скоріш за все таки буде Kotlin. І тут RxJava може використовуватися, як база (депенденсі) для RxKotlin.
Щоб багато не писати, раджу прочитати Т. Нуркевича (це якщо про RxJava) — там як раз про всі підходи і про scheduler і про бекпреже і про все інше (до речі це теж про ресурси).
Якщо для беку на Java — то «Hands-On Reactive Programming in Spring 5» — тут на початку про різне, потім вже про Reactor.
Для Android — то вже є що почитати на kodeco (взагалі про світ мобайлу). Але якщо цікаво, як це працює в інших мовах, то можна і по ним знайти і для C++ і для .Net.
І вже на останок — Rx — це функціональний підхід, замішаний на класичних патернах плюс конкаренсі.

Як раз керує ... Немає блокуючого виклику.

Знову ж, мова про ресурс у вигляді потоку. Я це не намагаюсь заперечити. Але стверджую, що kotlin coroutines з цим впорається значно краще. Саме в частині, що з корутинами логіка може бути нелінійною і код стає виглядати як «звичайний». Тобто ви пишете код, що дуже схожий на звичайний, а він при цьому скаче по різним pool-ам і чекає зовнішні «сигнали» в non-blocking режимі (але код виглядає так саме як і в blocking)

Kotlin не завжди підтримує останню JVM

Не зіштовхувався з цим, проте, якщо навіть в compile-time не використовуються останні фічі, то в runtime запускається на останній (як мінімум, проблем не пригадую з цим). Єдине що з котліном потрібно білд скрипти адаптовувати — що не зручно.

Т. Нуркевича

Проглянув цю книгу по діагоналі. Ви знаєте, дуже шкода, що я її не бачив тоді, коли сам з цим розбирався. Ця книга — мала б бути частиною документації :) Реально круто.

Зверніть увагу, я НЕ кажу, що проблемою є поріг входу. Поріг входу багато де високий. Я стверджую, по суті, дві тези:
— Rx архітектурно схильний до неочікуваної поведінки.
— Для будь-яких випадків, де корисним може бути Rx, вже є кращі альтерантиви.

основна проблема — зависокий поріг входу до технології.

до появи стейтів і бібліотек, для роботи з ними — ngrx, ngxs — ото було важко.

а зараз будь яку макаку можна посадити боілерплейт ворочати.

Нарешті хтось написав статтю про негативні сторони реактивщини.

Очевидно, что оно мне нафиг не нужно, потому что я даже не знаю, что это.

Дякую! Нарешті побачив хоч одну статтю, яка змістовно доводить що якась SuperJS бібліотека — це черговий типовий JS велосипед вигаданий нонейм саморбниками. І яка радить його НЕ юзати!
У світі бєкенду давно існують декілька фреймвоків від серйозних ІТ компаній, у яких вже вирішені усі розповсюджені бізнес-проблеми: таймзони, локалізація, глобалізація, дзеркальне відображення, різні календарі, різні валюти, підтримка аксесибіліті і ще багато іншого. Бекенд девелопери можуть «формошльопити» і навіть не замислюватись наскільки усе складно.
У світі JS — усе навпаки. Є 100500 фреймвоків для одного і того-ж. Але жодний з них не достатньо досконалий, аби покривати усі потреби. Тому навіть для тривіального JS аплікейшина треба збирати «зоопарк» JS бібліотек і потім намагатися їх склеїти своїми латками. Ще гірше, що мода кожен рік міняється — учора усі хвалили JQuery чи MomentJS — сьогодні кажуть: вони застарілі, неоптимальні — краще юзайте «Новий велосипед JS», який робить те саме, але через проміси чи через чисті функції чи через ще щось, що сьогодні стало модним у JS.
Сподіваюся з часом буде з’являтися усе більше таких статей на тему «не займайтеся самолікуванням — краще ідіть до доктора». Не беріть чиїсь JS велосипеди і не вигадуйте своїх — краще мати один перевірений підхід для усіх бізнес — аплікейшинів.

Так стаття про java а не js. І ця фігня має імплементацію на багатьох мовах

Насправді з JS світу, Nextjs намагається доволі якісно вирішувати всі бізнес-проблеми, SSR, 0Auth, дб-драйвери, роутинг працюють майже з коробки

використовую RxJS. так він неінтуїтивний і часом складний, але в результаті виходить дуже стислий і читабельний код.
forkJoin подобається. і оператори поменше теж чудові: distinctUntilChanged, auditTime

Оператори — чудові — я їх перелік, інколи, використовую як «довідник ідей» :)
Реалізація — «таке».

Трохи дописаний код з документації:

import { fromEvent, auditTime } from 'rxjs';

const clicks = fromEvent(document, 'click');
const result = clicks.pipe(auditTime(1000));
result.subscribe((x) => {
  console.log(x);
  throw Error();
});
Перший клік і відписка :(
Можливо, в js це не такий частий випадок, але exception-и трапляються.
Але Rx простіше не зробить — ви просто поміняєте одні граблі на інші

ну, mvvm + coroutines сильно простіше рішення

Хочете простіше — пишіть на Flutter/Dart — кращого підходу для UI я ще не бачив

Compose. на тому самому mvvm. ніяких bLock

Згоден з вами, mvvm + coroutines — це гарний підхід. По великому рахунку, той самий Flutter на цьому побудований. Та і React теж. Здавалося б ... наче близнюки фреймворки, але відчуваються вони якось зовсім по різному, наче спільного й нічого немає.

Щодо Jetpack Compose ... я на нього не дивився, але підхід, бачу, схожий. Відносно нова штука ... я так зрозумів 2021 появились перші версії. Flutter — теж новий відносно, але появився десь 2018 року.

По великому рахунку, той самий Flutter на цьому побудований

один з варіантів архітектури Flutter побудований на згаданому мною BLoC(реалізцій яких я бачив дві), як на мене не зовсім те рішення як у viewModel

Flutter — теж новий відносно, але появився десь 2018 року

ну, мені з декларативних фреймворків на очі першим трапився swiftUI, хоча Flutter дійсно почав розроблятись раніше... проте, повертючись до Android, то Compose має один великий плюс — для його реалізації не потрібно перелопачувати весь код, він нормально лягає на існуючі activity/fragments і юзає той самий mvvm + coroutines)))

Хочете простіше — пишіть на Flutter/Dart

Якась сумнівна порада.
Доречі, як на мене, використовувати RX для обробки натискань на кнопку у 2023 це нездорова історія.

Якась сумнівна порада.

Скоріше всього, ця порада — то дуже особисте враження. Коли я переходив з Java/Android на Flutter — це був як ковток свіжого повітря: перестаєш думати за життєвий цикл Activity, про Fragment забуваєш як про сутність, корутини інтегровані з коробки в gui thread — не треба винаходити «свою» асинхронщину, та й gui стає єдиним signle thread середовищем — як і має бути ... схопити race практично неможливо, Null Safety — окрема плюшка, тощо.

Єдине, що якщо потрібно, дійсно, робити якийсь background compute — то трохи складніше ... але якщо хочеться, це можна і на Java написати — інтегрується досить просто ... єдине, що під iOS теж доведеться це писати на Swift.

В контексті статті ж ... думка була в тому, що RX там впроваджувати не варто. Flutter — це одна з альтернатив. Якщо щось менш кардинальне, то як в сусідньому коментарі згадували, що Kotlin з корутинами — теж гуд.

використовувати RX для обробки натискань на кнопку у 2023 це нездорова історія.

Rx, взагалі, то нездорова історія ))

Підписатись на коментарі