Kotlin i niejednoznaczny null

0

Zainspirowany wątkiem o wyższości innych języków nad Javą, postanowiłęm sprawdzić Kotlina.
Zacząłem przepisywać projekt nad którym obecnie pracuje, póki co muszę przyznać, całkiem fajnie to wygląda, dużo mniej kodu, niektóre rzeczy bardzo ułatwiają pracę, ale chyba czegoś nie łapie...

Mam taką metodę w repozytorium

    fun findById(userId: String): UserProfile? {
        return mongoTemplate.findOne(userIdQuery(userId), UserProfile::class.java)
    }

I taki serwis do logowania użytkownika

@Service
class AuthenticationService(private val userRepository: UserRepository,
                            private val tokenProvider: JwtTokenProvider,
                            private val passwordEncoder: PasswordEncoder) {

    fun authenticate(input: AuthenticationInput): String? {
        val user = userRepository.findByEmail(input.email)
        require(user != null ) {"User ${input.email} was not found"}
        require(validPassword(input.password, user.password)) {"Password is incorrect"}
        return tokenProvider.createToken(user.id);
    }

    fun validPassword(providedPassword: String, actualPassword: String): Boolean {
        return passwordEncoder.matches(providedPassword, actualPassword);
    }
}

No i wywala to błąd, który wiem jak poprawić ale nie do końca rozumiem czemu on tam jest?
screenshot-20201127212707.png

  • findByEmail() może zwrócić nulla, ok, w następnej linijce jednak to sprawdzam
  • metoda validPassword nie akceptuje nullowych wartości, ale mimo wszystko IDE nie krzyczy, że nie wolno - czy to dlatego, że wcześniej jest require != null?
  • jeżeli tak, to dlaczego createToken(), który równeż wymaga nie nullowej wartości, nagle ma z tym problem?

No i dwa pytania poboczne

  • wyczytałem, że require jest ok do takich powiedzmy biznesowych walidacji, prawda to?
  • nie widzę w Kotlinie Optional, poza tym z java.util, jak obsługiwać takie przypadki jak ten z repo? w Javie zwracanym typem był po prostu Optional<UserProfile>, czy w Kotlinie też korzystać z Javowego Optional czy potrzebuje Option z Vavra, czy zwracanie nulla jest ok bo i tak muszę go obsłużyć?
2

1.W Kotlinie null jest ok
2.A co przymuje metoda createToken? Bo zdaje sę że przekazujesz tam user.id które może być nullem

0

@scibi92:

Właśnie miałem edytować posta, znalazłem swój błąd, tak jak mówisz... z wiadomych przyczyn id musi być tutaj nullable

@Document
data class UserProfile(
        @Id
        val id: String? = null,
        val email: String,
        val password: String,
}

No a createToken() oczekuje na non-nullable Stringa

No to w takim razie, co z required? Mogę stosować do obsługi logiki biznesowej? Czy to antypatern jak rzucanie wyjątków w Javie przy każdej możliwej okazji?

1

Zalezy czy chcesz IllegalArgumentException czy bawic sie w jakies monady lub T?.

Jesli haslo sie nie zgadza no to generalnie da sie to naprawic proszac uzytkownika o ponowna probe. Tym zreszta moze sie pomylka zdarzyc, wiec moze to nie taka sytuacja wyjatkowa?

PS: Takie jednolinijkowe wyrazenia mozesz zapisac krocej jako

    fun validPassword(providedPassword: String, actualPassword: String): Boolean =
        passwordEncoder.matches(providedPassword, actualPassword)
    

    // a nawet 
    fun validPassword(providedPassword: String, actualPassword: String) =
        passwordEncoder.matches(providedPassword, actualPassword) // ale niekoniecznie najlepsze w publicznych metodach

EDIT: Jednolinijkowe to byl zbyt duzy skrot myslowy. Generalnie wyrazenia.

0

Właśnie zauważyłem, że required rzuca IllegalArgumentException, a we wcześniejszym podejściu w Javie chciałem uniknąć rzucania wyjątków w takiej sytuacji. Tak to wyglądało w Javie

public class OperationResult<T> {
    private final Either<List<Error>, T> result;

    private OperationResult(Either<List<Error>, T> result) {
        this.result = result;
    }

    public static OperationResult<Success> success(String message) {
        return new OperationResult<>(Either.right(new Success(message)));
    }

    public static <T> OperationResult<T> success(T object) {
        return new OperationResult<>(Either.right(object));
    }

    public static <T> OperationResult<T> failure(Seq<Error> errors) {
        return new OperationResult<>(Either.left(errors.toJavaList()));
    }

    public static <T> OperationResult<T> failure(Error... errors) {
        return new OperationResult<>(Either.left(Arrays.asList(errors)));
    }

    public Either<List<Error>, T> getResult() {
        return result;
    }    
}

No i sam AuthenticationService wyglądał tak

    public OperationResult<AuthenticationResult> authenticate(AuthenticationInput command) {
        return userRepository.findByEmail(command.getEmail())
                .map(user -> validatePassword(user, command))
                .orElseGet(() -> failure(Error.USER_NOT_FOUND));
    }

    private OperationResult<AuthenticationResult> validatePassword(User user, AuthenticationInput command) {
        return passwordEncoder.matches(command.getPassword(), user.getPassword()) ?
                success(authenticationToken(user)) :
                failure(Error.INCORRECT_PASSWORD);
    }

    private AuthenticationResult authenticationToken(User user) {
        return new AuthenticationResult(tokenProvider.createToken(user.getId()));
    }

Error to po prostu enum.
Jest podobny mechanizm w Kotlinie czy tak jak w Javie - Vavr i piszę to samemu?

1

Polecam użyć sealed class:

class AuthenticationService(
    private val userRepository: UserRepository
) {
    fun authenticate(input: AuthenticationInput): AuthenticationResult =
        userRepository.findByEmail(input.email)
            ?.let { user -> if (passwordValid(input.password, user.password)) AuthenticationResult.Ok else AuthenticationResult.IncorrectPassword }
            ?: AuthenticationResult.UserNotFound

    private fun passwordValid(givenPassword: String, userPassword: Any): Boolean {
        TODO("Not yet implemented")
    }
}

sealed class AuthenticationResult {
    object Ok : AuthenticationResult()
    object UserNotFound : AuthenticationResult()
    object IncorrectPassword : AuthenticationResult()
}

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