Vavr - łączenie dwóch Either

0

Cześć,

Zaczynam się uczyć Vavr'a i zatrzymałem się na Either. Mam taki przypadek:

class TaskService {

/* Reszta pól */
private final UserService userService;
private final TaskRepository taskRepository;

    public Either<TaskError, TaskDto> createTask(CreateTaskDto createTaskDto) {
        Either<TaskError, User> user = userService.findById(createTaskDto.getUserId())
                .toEither(TaskError.USER_NOT_FOUND);

        Either<TaskError, Task> taskToSave = Option.of(createTaskDto)
                .toEither(TaskError.NO_TASK_PROVIDED)
                // ... jakies validatory dodatkowo na dto
                .map(task -> modelMapper.map(task, Task.class));

        return ... ?
    }
// na końcu, gdy nie będzie żadnych 'left value'  biorę obiekt usera, dodaję task'a, a potem zapisuje go.
public class User {
     /* Reszta pól */
     private List<Task> tasks = new ArrayList<>();

     public void addTask(Task task) {
          this.tasks.add(task);
          task.setUser(task);
     }
}

Kombinowałem na różne sposoby to jakoś żenić ze sobą, ale bez efektów. Powinienem to starać się zrobić w jednym streamie, ciągiem, czy może rozbić tak jak mam to teraz zrobione, a następnie je razem połączyć?

3

flatMap, kiedy Either jest right to flatMap przemapuje całośc w nowego Either
Przykład:

Either<BetError, BetDTO> updateBetToMatch(UUID betUUID, BetType betType) {
        return repository
                .findOne(betUUID)
                .toEither(BetError.BET_NOT_FOUND)
                .flatMap(betValidator::canUpdate) //canUpdate zwraca kolejnego Either
                .map() //tu co tam dalej chcesz
    }
0

(No tak!) Racja, niby tak naprawdę wiedziałem, ale jakoś nie chciało to do mnie dotrzeć.

@catom
Prawda, tak bardzo skupiłem się na tym całym Vavr'rze (ah ta odmiana), że wlazłem w NPE - no, ale o tym i tak jeszcze raz za chwile.

@danek
Dzięki za podpowiedź. Tak naprawdę, to podczas nauki Vavr'a to nieco wzoruje się na twoim projekcie Bet'ów oraz postu na mikroblogu o sposobie handlowania błędów razem z Either (*Error oraz ResponseResolver)

Odnośnie samego problemu to mam wrażenie, że nieco krzywo opisałem o co mi chodzi...

Załóżmy, że mamy przypadek gdzie musimy wykonać kilka różnych metod na kilku różnych serwisach które oczywiście (a może jednak nie tak oczywiste?) zwracają różny LeftValue (*Error).

 public Either<OperacjaError, UdaloSieHurraDto> doSomething(DoSomethingDto doSomething) {

        Either<RedError, RedResponse> red = redService.doSomething(...);
        Either<GreenError, GreenResponse> green = greenService.doSomething(...);
        Either<BlueError, BlueResponse> blue = blueService.doSomething(...);
        
        return ...;
    }

Zakładam, że taka sytuacja jest całkiem niewygodna, bo trzeba by mapować każdy LeftValue na nasz OperacjaError. Pytanie, czy jest to oznaka, że coś robimy nie tak?
Czy w ramach zawołania metody na serwisie powinniśmy się poruszać po tylko i wyłącznie jednym obiekcie błędów?
Np. taki TaskError, który załączyłem w pierwszym poście... posiada on wartość "USER_NOT_FOUND". Właściwie to jest ona powielona, ponieważ UserError również taki błąd posiada (z drugiej strony to najzwyczajnieszych błędem z punktu widzenia dodawaniu nowego taska jest nieistniejący użytkownik, więc 'konceptualnie' chyba jest ok). Zrobiłem tak głównie po to, żeby oszczędzić sobie zbędnego mapowania. Z kolei taka "OperacjaError" może posiadać te same wartości błędów i co Red/Green/Blue, tylko po to, żeby nie mapować, a jeśli już mapować to 1:1.
Nie jestem pewien czy wyraziłem się jasno, ciężko to wszystko opisać, ale jeśli zrobiłem to wystarczająco dobrze - jak radzicie sobie w takich sytuacjach?

Przykład mapowania z projektu @danek który jest tu

 PointsError mapLeagueFacadeError(LeagueError error) {
        return error == LeagueError.SET_NOT_SET_RESULT ? PointsError.SET_NOT_SET_RESULT : PointsError.MATCH_NOT_FOUND;
    }

Osobne pytanie mam co do samego sprawdzania argumentów. Teoretycznie praktycznie zawsze możemy sprawdzać czy argumenty w danej metodzie nie będą przypadkiem null, a czasami nawet to kod nie przechodzi review jeśli takich sprawdzeń nie ma.

Zgodnie z tym, na co zwrócił uwagę catom w komentarzu :

Option.of(createTaskDto)
.toEither(TaskError.NO_TASK_PROVIDED)

Oczywiście abstrahując od tego, że tam jest potworne NPE (mea culpa) to opakowując to w Option'a nie mam tak naprawdę na myśli, że 'może go nie być', tylko to, że jak ktoś mi tego nulla jednak wrzuci to się nie wysypie - a nawet zarzuci mi od razu LeftValue jako dedykowany "NO_TASK_PROVIDED".
Pytanie nieco egzystencjonalne, i może nieco zależne od planowanej architektury - ale czy takie 'sprawdzacze' koniec końców w ogóle mają sens?
Jak to robicie w swoich projektach?

1

Co do tych samych błędów w różnych modułach.
Zależy w co chcesz iść. Jeśli moduły mają być od siebie całkowicie niezależne (wtedy łatwiej podzielisz je np na osobne mikroserwisy) to niestety obiekty błędów muszą się powtarzać. Z drugiej strony, możesz pójść na kompromis i mieć jakiś jeden interface na całą aplikacje i w lewej częsci Eithera mieć zawsze ten interface. Wtedy nie musisz nic mapować, bo nie obchodzi Cie dokładnie co tam siedzi, ważne, że błąd. Coś podobnego testuje w swoim innym projekcie, możesz tam zerknąć ;)

Co do nulli. Staraj się przede wszystkim w ogole nie przekazywać nulli do metod, wtedy nie musisz nic sprawdzać ;)

0

Jak pisał danek, utworzenie jakiegoś interfejsu dla Twoich błędów. Na koniec, możesz zrobić jakąś namiastkę pattern matchingu (np. wykorzystując Vavrowy Pattern matching).

W takiej Scali nie miałbyś tego problemu dzięki zdecydownaie lepszej obsłudzie wariancji niż w generykach Javowych.

@danek

Co do nulli. Staraj się przede wszystkim w ogole nie przekazywać nulli do metod, wtedy nie musisz nic sprawdzać ;)

Poza sytuacjami, w których dostajesz dane od jakiegoś systemu zewnętrznego i dane pole jest opcjonalne.
Wówczas, musimy przynajmniej w mapperze wartości z zewnątrz obsłużyć ten nulle, ale zgadzam się, że w ramach swojej logiki, najlepiej nie przkazywać nulli do swoich metod.

1 użytkowników online, w tym zalogowanych: 0, gości: 1