Łączenie wyników kilku Eitherów

Odpowiedz Nowy wątek
2019-08-10 14:38
0

Piszę swoją aplikację korzystając z biblioteki Vavr i flow programu jest sterowany za pomocą Either.

Mam jednak mieszane uczucia czy w ogóle dobrze do tego podchodzę...
Mam metodę która tworzy użytkownika, pod spodem której dzieje się całkiem sporo
-sprawdzana jest poprawność danych
-walidacja czy taki użytkownik już nie istnieje
-generowanie tokenu aktywacji konta
-zapisanie użytkownika na bazie
-wysyła maila z linkiem do aktywacji

I większość z tych metod, które są wołane, zwraca Either<Error, Success>

Po kolei wygląda to tak, sama metoda createUser oraz metody pomocnicze

 Either<DomainError, SuccessMessage> createUser(RegisterUserDto registerUserDto) {
        var validationError = validate(registerUserDto);
        if(validationError.isPresent()) return Either.left(validationError.get());
        final var token = generateToken(registerUserDto.getUsername());
        final var user = User.createUser(registerUserDto);
        final var savedUser = user.map(this::saveUser);
        return savedUser.isRight() && token.isRight() ?
                emailFacade.sendActivationEmail(registerUserDto.getUsername(), registerUserDto.getEmail(), token.get())
                : Either.left(savedUser.getLeft());
    }

 private Optional<DomainError> validate(RegisterUserDto userDto) {
        if(userRepository.findByUsername(userDto.getUsername()).isPresent()) return Optional.of(UserError.USERNAME_ALREADY_EXISTS);
        if(userRepository.findByEmail(userDto.getEmail()).isPresent()) return Optional.of(UserError.EMAIL_ALREADY_EXISTS);
        return Optional.empty();
    }

private Either<DomainError, String> generateToken(String username) {
        return Try.of(() -> tokenRepository.generateToken(username))
                .onFailure(e -> log.severe(e.getMessage()))
                .toEither(UserError.PERSISTENCE_FAILED);
    }

    private Either<DomainError, User> saveUser(User user) {
        return Try.of(() -> save(user))
                .onFailure(e -> log.severe(e.getMessage()))
                .toEither(UserError.PERSISTENCE_FAILED);
    }

    private User save(User user) {
        userRepository.saveUser(objectMapper.userToDto(user));
        return user;
    }

Encja usera

  static Either<DomainError, User> createUser(RegisterUserDto dto) {
        return validEmail(dto.getEmail()) ?
            Either.right(User.builder()
                    .username(dto.getUsername())
                    .password(dto.getPassword())
                    .email(dto.getEmail())
                    .active(false)
                    .roles(Set.of("USER"))
                    .build()) :
            Either.left(UserError.INVALID_EMAIL);
    }

No i inna domena która jest tu wołana

 public Either<DomainError, SuccessMessage> sendActivationEmail(String username, String receiver, String token) {
        return emailService.sendEmail(username, receiver, token);
    }

No i zastanawiam się czy to w ogóle ma ręce i nogi, ciężko o jakieś bardziej skomplikowane przykłady w Internecie więc fajnie jakby ktoś kto korzysta z tego na co dzień spojrzał krytycznym okiem.

Abstrachując od samego Vavra, Optional#isPresent zwykle wskazuje na code smell. Poza tym Vavr ma swojego Option, może da się to ładniej schainować bez tych checków isLeft, isRight, isPresent. Jak się nie da to bez sensu cała idea IMO - Charles_Ray 2019-08-10 14:59

Pozostało 580 znaków

2019-08-10 16:58
1

Jeśli dobrze zaplanujesz flow, da się taki ciąg wywołań zastąpić listą map()/flatMap() i możesz osiągnąć przykładowo coś takego:

synchronized Either<UserError, UserEntity> registerUser(LoginUserInfo user) {
        return userValidator
                .validate(user)
                .map(this::createNormalUserEntity)
                .map(this::saveToRepository);
    }

Cała 'trudność' polega na tym, żeby odpowiednie operacje (te które podałeś w poście) odpowiednio podzielić na warstwy, czyli nie mieć jednego ciągu wywołań w jednym miejscu, a rozdzielić na odpowiednie obiekty (tak jak np tutaj całą walidacje mam przeniesioną gdzieś indziej, u Ciebie byłoby np. wysyłka maila byłby gdzieś 'wyżej' niż samo rejestrowanie użytkownika)


Spring? Ja tam wole mieć kontrole nad kodem ᕙ(ꔢ)ᕗ
Haste - mała biblioteka do testów z czasem.

Pozostało 580 znaków

2019-08-10 18:30
0

Chyba udało mi się to trochę uprościć

 Either<DomainError, SuccessMessage> createUser(CreateUserDto userDto) {
        var validationErrors = hasValidationErrors(userDto);
        return validationErrors.isPresent() ? Either.left(validationErrors.get()) : create(userDto);
    }

    private Either<DomainError, SuccessMessage> create(CreateUserDto userDto) {
        return User.createUser(userDto)
                .flatMap(this::saveUser)
                .flatMap(this::generateToken)
                .flatMap(token -> sendEmail(userDto, token));
    }

I nawet to działa. ale chciałbym się pozbyć tego isPresent i zrobić np.

Either<DomainError, SuccessMessage> createUser(CreateUserDto userDto) {
        return hasValidationErrors(userDto).map(DomainError::toEitherLeft).orElse(create(userDto));
    }

Problem w tym, że pomimo tego że podaje request który nie przechodzi walidacji i w odpowiedzi dostaje poprawny komunikat, to i tak wykonuje się część w orElse, czyli zapis na bazie, wysłanie maila... Nie da się tego zapisać za pomocą map/ifPresent?

Pozostało 580 znaków

2019-08-10 18:33
0

A czemu nie po prostu? ;)

Either<DomainError, SuccessMessage> createUser(CreateUserDto userDto) {
        return validate(userDto)
           .map(this::create)
           .mapLeft(DomainError::toEitherLeft);
    }

Spring? Ja tam wole mieć kontrole nad kodem ᕙ(ꔢ)ᕗ
Haste - mała biblioteka do testów z czasem.
edytowany 1x, ostatnio: danek, 2019-08-10 18:33

Pozostało 580 znaków

2019-08-10 18:44
0
danek napisał(a):

A czemu nie po prostu? ;)

Either<DomainError, SuccessMessage> createUser(CreateUserDto userDto) {
        return validate(userDto)
           .map(this::create)
           .mapLeft(DomainError::toEitherLeft);
    }

Rozumiem że validate() musiałoby zwracać Either<DomainError, SuccessMessage> ?
screenshot-20190810184416.png

albo to albo Either<List<DomainError>, SuccessMessage> - danek 2019-08-11 01:40

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

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