BG Development


Страници: (2) [1] 2   ( Първото ново мнение ) Reply to this topicStart new topicStart Poll

> Логически операции в RxJava поток
thrawn
Публикувано на: 20-02-2025, 08:58
Quote Post



Име:
Група: Потребител
Ранг: Почетен член

Мнения: 3730
Регистриран на: 17.01.17



Играя си с една имплементация на web сървиз която искам да направя реактивна.
Самото API ми връща Response обект (който съдържа http статусът). При успех на заявката извиквам onNext с получения response обект а при изключение onError (със самото изключение).

Проблемът в постановката е, че при subscribe получавам response обектът на едно място, Независимо от http статусът.

Логичното решение в, да използвам обикновен if/else за да определя статусът и да си извикам съответната логика:
CODE

Observable<Response> service = .....;

service
   .subscribe(response -> {
       if(isSuccess(response)) {
           onResponseOk(response);
       } else {
           onResponseError(response);
       }
   }, Main::onError);


Това работи перфектно, просто е и четливо. Ама на някой места, гледам че се обясняват как не било реактивно. На други твърдят, че няма проблеми ...

Та втори опит.
Правя два абоната И във всеки филтрирам отговорите на база HTTP статус:
CODE

Observable<Response> service = .....;

service
   .filter(Main::isSuccess)
   .subscribe(Main::onResponseOk, Main::onError);

service
   .filter(Main::isNotSuccess)
   .subscribe(Main::onResponseError, Main::onError);


Това работи горе долу добре (с hot observable) но има недостатък, че при изключение Main::onError се извиква два пъти.

Трети опит.
Правя два потока (изглежда ми ненужно) в които филтрирам по HTTP статус, Задавам междинни манипулатори на onNext и след това обединявам потоците.
CODE

Observable<Response> service = .....;

service.flatMap(response -> {
   Observable<Response> ok = Observable.just(response)
                                           .filter(Main::isSuccess)
                                           .doOnNext(Main::onResponseOk);

   Observable<Response> error = Observable.just(response)
                                               .filter(Main::isNotSuccess)
                                               .doOnNext(Main::onResponseError);

   return Observable.merge(ok, error);
})
.subscribe(response -> {}, Main::onError);


Това работи добре, но ми изглежда малко пресилено.

Въпросът ми е, как се решава подобен проблем в реактивното програмиране? И има ли изобщо смисъл да се вкарвам в подобно приключение след като мога просто да използвам if/else (вариант 1)?
PMEmail Poster
Top
thrawn
Публикувано на: 20-02-2025, 09:58
Quote Post



Име:
Група: Потребител
Ранг: Почетен член

Мнения: 3730
Регистриран на: 17.01.17



Следващ опит, който изглежда малко по-чисто разчита на мой оператор
CODE

Observable<Response> service = .....;

service
   .lift(takeResponse(Main::onResponseOk, Main::onResponseError))
   .subscribe(response -> {}, Main::onError);


Но пък самата имплементация на оператора използва if/else
CODE

private static @NonNull ObservableOperator<Response, Response> takeResponse(final Consumer<Response> ok, final Consumer<Response> err) {
   return new ObservableOperator<Response, Response>() {

       @Override
       public @NonNull Observer<? super Response> apply(@NonNull Observer<? super Response> observer) throws Throwable {
           return new DisposableObserver<Response>() {
               @Override
               public void onNext(@NonNull Response response) {
                   if(response != null) {
                       if(isSuccess(response)) {
                           ok.accept(response);
                       } else {
                           err.accept(response);
                       }

                       onComplete();
                   }

                   observer.onNext(response);
               }

               @Override
               public void onError(@NonNull Throwable e) {
                   observer.onError(e);
               }

               @Override
               public void onComplete() {
                   observer.onComplete();
               }
           };
       }
   };
}
PMEmail Poster
Top
thrawn
Публикувано на: 20-02-2025, 10:30
Quote Post



Име:
Група: Потребител
Ранг: Почетен член

Мнения: 3730
Регистриран на: 17.01.17



Същия подход го приложих, като се абонирах с мой observer.

CODE

Observable<Response> service = .....;

service.subscribe(takeResponse(Main::onResponseOk, Main::onResponseError, Main::onError, () -> {}));


И самата фабрика
CODE

private static Observer<? super Response> takeResponse(final Consumer<Response> ok, final Consumer<Response> err, final Consumer<Throwable> onError, final Runnable onComplete) {
   return new DisposableObserver<>() {
       @Override
       public void onNext(@NonNull Response response) {
           if(response != null) {
               if(isSuccess(response)) {
                   ok.accept(response);
               } else {
                   err.accept(response);
               }
           }
       }

       @Override
       public void onError(@NonNull Throwable e) {
           onError.accept(e);
       }

       @Override
       public void onComplete() {
           onComplete.run();
       }
   };
}


Всичко е ОК, само дето това е пряко повторение на първия вариант, но с повече код...

Ако някой има идеи как е редно да се подходи да казва icon_smile.gif
PMEmail Poster
Top
relax4o
Публикувано на: 20-02-2025, 21:45
Quote Post



Име:
Група: Потребител
Ранг: Почетен член

Мнения: 2829
Регистриран на: 04.04.07



А кога хвърля изключение? Предполагам при неуспех да се свърже и подобни мрежови проблеми?

Честно казано, първия вариант, който си дал, според мен, е напълно валиден практически. Това е масов проблем(или пък не) с http client-и. Може да се замаже с един map() и фасада в сървиза, който извиква http client-а (или каквото е там).

CODE

// service
function Observer<...> getMeEverything(bla bla) {
    return httpClient.get(...).map(...);
}

// където извикваш сървиза
service.getMeEverything().subscribe(...)


Решението във втория пост е също добро според мен и го предпочитам пред третия ти пост.

Така или иначе от if/else не можеш да избягаш, освен ако не можеш да имплементираш интерсептор на всички заявки и при response на >=400 да хвърляш къстъм изключение, което не вярвам да предпочиташ като решение. Въпреки че и там ще имаш проверка поради естеството на това как работи клиента.

Не съм ползвал Java, но като гледам работи на подобен принцип като в Angular и rxjs.


--------------------
Бисери :D

QUOTE (oveRLuckEd)
Ползваш някоя нова версия на PHP, която е вече ооп ориентирана и заради това ти я изкарва тази грешка.


QUOTE (nbacool2)
Щом няма input полета, значи няма откъде да се направи SQL инжекция Very Happy
PM
Top
thrawn
Публикувано на: 21-02-2025, 08:45
Quote Post



Име:
Група: Потребител
Ранг: Почетен член

Мнения: 3730
Регистриран на: 17.01.17



Ами RxJava е имплементация за java на реактивното api на reactivX. RxJS съответно е имплементацията за javascript. Та двете би следвало да са почти еднакви (с дребни езикови различия де). Така че, каквото е валидно за javascript в rxjs би трябвало да е валидно и тук (та java практически не ти трябва. Спокойно можеш да даваш решение/я на javascript).

Генерирането на самия Observable обект не е интересно (спецификата, че става дума за web api). Емитнатите данни могат да са дори прости числа и да трябва да предприемем различно действие в зависимост от самото число.

Основния момент тук е, че се въвежда терминът "реактивно програмиране" и съответно се почват някакви разпалени обяснения как if/else, for ... не бива да се ползват в контекстът на реактивното програмиране. Съответно почват да се появяват подобни тривиални проблеми (Може би, от липса на опит, знам ли. Затова и пуснах темата).

Та като се замисля, когато в java въведоха stream апи-то (синтактично няма разлика с реактивното апи) коментирахме точно итерирането на потоци. Та тогава, ако не се лъжа Stilgar каза, че като свикна да го ползвам ще ми е търсене да пиша цикли, и беше прав. Сега почти винаги го ползвам. Затова, ако направим аналогия между двете, то избягването на подобни структури се прилага само при модифициране и/или трансформиране на потока или данните в него. Докато обработката на самите данни (било в крайните точки, или в междинните) може да се извърши както е по-удачно. А причината за различните мнения е, че хората които са прекалено ентусиазирани от самото апи генерализират докато хората с опит (може би) казват, че няма лошо да се ползва императивен стил на програмиране във функциите.

Това е първия вариант по двата начина
CODE

service
  .subscribe(response -> {
      if(isSuccess(response)) {
          onResponseOk(response);
      } else {
          onResponseError(response);
      }
  }, Main::onError);


CODE

service
   .subscribe(response -> {
       Observable.just(response)
           .filter(Main::isSuccess)
           .subscribe(Main::onResponseOk);

       Observable.just(response)
           .filter(Main::isNotSuccess)
           .subscribe(Main::onResponseError);
   }, Main::onError);


---
Що се касае до самото api, то изключенията са в следствие на проблеми я по връзката, я с парсването на данните. Абе грешки. Докато http статус кодовете, въпреки че, носят информация за грешка то не винаги се третират като такава. Да речем 401 ще предизвика влизане в процедура по автентикация (логин). 404 при заявка за търсене не се третира като грешка а отговор няма данни (вместо празен обект). 500 в повечето случаи носи информация за настъпено ограничение в базата данни (нещо вече съществува, друго не може да бъде изтрито защото се използва). И въпреки, че е възможно всичко това да се вкара в изключения то употребата на try/catch за управление на бизнес логика в приложение е много неудачно.

Всичко дискутирано до тук приема за успешни заявки с код 200 - 299

Това мнение е било редактирано от thrawn на 21-02-2025, 09:00
PMEmail Poster
Top
relax4o
Публикувано на: 21-02-2025, 11:55
Quote Post



Име:
Група: Потребител
Ранг: Почетен член

Мнения: 2829
Регистриран на: 04.04.07



Да ти кажа, работа с http клиенти се различава до някъде от реактивно програмиране на каквото и да е друго, защото като програмисти всичко от 400 нагоре го третираме като грешка и очакваме да го обработваме като такава в onError, но тука случая е друг, защото имаш и изключения за реални грешки, а всички статуси се третират като валиден response.

За това понякога трябва да си прагматичен с решенията, колкото и да не ти харесват.

Практически в Ангулар аз лично правя нужното трансформиране в сървиза, където се използва http клиента, след отговор от АПИ-то. Избягвам да трябва да върша много проверки там, където се събскрайбвам (по компонентите), защото прави кода нечетим.

Също избягвам subscribe() в subscribe() или в който и да е метод по чейна, защото при промяна по observable не ти гарантира канселиране на вътрешните събове. По-добре е да ползваш switchMap/flatMap/concatMap в такива случаи.

Аз лично твоя случай сигурно ще го реша с прост map в сървиза, както посочих в предния си пост.

CODE

// service
function Observer<...> getMeEverything(bla bla) {
   return httpClient.get(...).map(response => isSuccess(response) ? okResponse(response) : errorResponse(response));
}

// където извикваш сървиза
service.getMeEverything().subscribe(...)


Редовна практика е при нас (може би и на нас ни куца опит) да ремапнем отговора, но го правим в сървиза, за да можем в компонентите чисто да се абонираме и да използваме отговора. Вече, ако има конкретна логика, която да трябва да се изпълни в компонента, си я правим с subscription-а.

Като гледам и Нетфликс използват същата практика като мен.
https://speakerdeck.com/benjchristensen/fun...m-2013?slide=45 . Можеш да разгледаш всички слайдове отначало, ако го намериш за интересно.



--------------------
Бисери :D

QUOTE (oveRLuckEd)
Ползваш някоя нова версия на PHP, която е вече ооп ориентирана и заради това ти я изкарва тази грешка.


QUOTE (nbacool2)
Щом няма input полета, значи няма откъде да се направи SQL инжекция Very Happy
PM
Top
thrawn
Публикувано на: 21-02-2025, 12:50
Quote Post



Име:
Група: Потребител
Ранг: Почетен член

Мнения: 3730
Регистриран на: 17.01.17



В примерът ти се разчита на това, че httpClient е реактивен (предполагам) и сам ще си прихване и емитне изключенията. Защото ако не е така, то ще има проблем при липса на връзка да речем.

Иначе, в ревюто което си дал (точно линкнатите слайдове с map) наистина си ползват императивна логика. За съжаление, при представянето на обсървърът са свели примерът до println. Но като цяло почва да ми се затвърждава мнението, че генерализирането на темата е погрешно.

Имай в предвид, че това което давам като примери е гола работна постановка (набита в статичен контекст). В крайна сметка, api-то ще следва същия синтаксис като потребителя ще трябва да посочи поне един от трите манипулатора onResponseOk, onResponseError и onError (е, плюс посочване на remote интерфейс и подаване на съответните параметри). Реално от вън няма да се вижда конкретната имплементация.

Това мнение е било редактирано от thrawn на 21-02-2025, 13:00
PMEmail Poster
Top
goro
Публикувано на: 21-02-2025, 12:56
Quote Post



Име:
Група: Потребител
Ранг: Активен

Мнения: 171
Регистриран на: 31.05.09



QUOTE (thrawn @ 20-02-2025, 08:58)
Въпросът ми е, как се решава подобен проблем в реактивното програмиране? И има ли изобщо смисъл да се вкарвам в подобно приключение след като мога просто да използвам if/else (вариант 1)?

Аз, като любител, надничащ тук основно за да се ограмотявам, бих ползвал if/else, ако другия вариант не ми дава нещо повече.
PM
Top
thrawn
Публикувано на: 21-02-2025, 13:06
Quote Post



Име:
Група: Потребител
Ранг: Почетен член

Мнения: 3730
Регистриран на: 17.01.17



QUOTE (goro @ 21-02-2025, 12:56)
QUOTE (thrawn @ 20-02-2025, 08:58)
Въпросът ми е, как се решава подобен проблем в реактивното програмиране? И има ли изобщо смисъл да се вкарвам в подобно приключение след като мога просто да използвам if/else (вариант 1)?

Аз, като любител, надничащ тук основно за да се ограмотявам, бих ползвал if/else, ако другия вариант не ми дава нещо повече.

Тук става дума за принципен проблем. Не получаваш нищо в повече или по-малко. Просто подходът е различен.
В императивното програмиране сам си взимаш данните докато в реактивното чакаш някой да ти ги сдъвче и изплюе на готово icon_smile.gif
PMEmail Poster
Top
relax4o
Публикувано на: 21-02-2025, 13:50
Quote Post



Име:
Група: Потребител
Ранг: Почетен член

Мнения: 2829
Регистриран на: 04.04.07



QUOTE (thrawn @ 21-02-2025, 12:50)
В примерът ти се разчита на това, че httpClient е реактивен (предполагам) и сам ще си прихване и емитне изключенията. Защото ако не е така, то ще има проблем при липса на връзка да речем.

Иначе, в ревюто което си дал (точно линкнатите слайдове с map) наистина си ползват императивна логика. За съжаление, при представянето на обсървърът са свели примерът до println. Но като цяло почва да ми се затвърждава мнението, че генерализирането на темата е погрешно.

Имай в предвид, че това което давам като примери е гола работна постановка (набита в статичен контекст). В крайна сметка, api-то ще следва същия синтаксис като потребителя ще трябва да посочи поне един от трите манипулатора onResponseOk, onResponseError и onError (е, плюс посочване на remote интерфейс и подаване на съответните параметри). Реално от вън няма да се вижда конкретната имплементация.

В примера ми се разчита на това - да, защото практиката ми е такава в Ангулар, но винаги можеш да обърнеш отговора от non-reactive http client в observable и да го третираш така отвън, ако смяташ, че си заслужава да създаваш фасадата.

Общо взето, понякога трябва да сме прагматични и да изберем решението, което най-много ни утърва в случая. Няма много къде да се сгреши практически, освен когато хората викат subscribe() в друг subscribe() вместо да ползват switchMap(и подобните) и се чудят защо има необясними странични ефекти.



--------------------
Бисери :D

QUOTE (oveRLuckEd)
Ползваш някоя нова версия на PHP, която е вече ооп ориентирана и заради това ти я изкарва тази грешка.


QUOTE (nbacool2)
Щом няма input полета, значи няма откъде да се направи SQL инжекция Very Happy
PM
Top
3 потребители преглеждат тази тема в момента (3 гости, 0 анонимни потребители)
Потребители, преглеждащи темата в момента:

Topic Options Страници: (2) [1] 2  Reply to this topicStart new topicStart Poll

 


Copyright © 2003-2019 | BG Development | All Rights Reserved
RSS 2.0