Reputation: 113
I have an AccountCreator class with a create method that takes a DTO with the data needed to create an account. At the beginning there is an attempt to create 2 value objects (UserName and Password), then validate the uniqueness of the user name, create the Account entity which takes these 2 value objects in the constructor and save it in the repo. Of course, errors such as incorrect password length, etc. may be returned. I used Eithers for this and now the question is whether this code is ok or maybe it can be written somehow better?
public Either<Error, AccountDto> create(AccountCreateDto accountCreateDto) {
var errorType = ErrorType.ACCOUNT_PERSISTENCE_ERROR;
var errorMessage = "Not unique user name: " + accountCreateDto.userName;
var error = new Error(errorType, errorMessage);
return UserName
.create(accountCreateDto.userName)
.flatMap(userName ->
userNameUniquenessChecker.isUnique(userName.text) ?
Password
.create(accountCreateDto.password)
.flatMap(password -> {
var createdAccount = new Account(
userName,
password,
AccountStatus.OPEN,
LocalDateTime.now(),
new ArrayList<>()
);
var addedAccount = accountRepository.add(createdAccount);
var accountDto = new AccountDto(
addedAccount.userName.text,
addedAccount.password.text,
addedAccount.status,
addedAccount.creationDate,
(long) addedAccount.tasks.size()
);
return Either.right(accountDto);
}) : Either.left(error));
}
Upvotes: 1
Views: 140
Reputation: 9408
The customary FP approach would be to use a Validation
style applicative functor - there's one in Vavr called Validation. You can use Validation.combine
to combine multiple Validation values into one, e.g.:
public Validation<Seq<<Error>, AccountDto> create(AccountCreateDto accountCreateDto) {
Validation<Seq<<Error>, Account> validAcc =
Validation.combine(
UserName.create(accountCreateDto.userName),
Password.create(accountCreateDto.password)
).ap((un, pw) -> new Account(
un,
pw,
AccountStatus.OPEN,
LocalDateTime.now()
);
Validation<Seq<<Error>, Account> validAcc2 =
validAcc.flatMap(acc -> validateUserIdIsUnique(acc));
Validation<<Seq<Error>, AccountDto> validAccDto =
validAcc2.map(accountRepository::add)
.map(addedAccount ->
new AccountDto(
addedAccount.userName.text,
addedAccount.password.text,
addedAccount.status,
addedAccount.creationDate,
(long) addedAccount.tasks.size()
)
);
}
return validAccDto;
}
private static final var errorType = ErrorType.ACCOUNT_PERSISTENCE_ERROR;
private static final var errorMessage = "Not unique user name: " + accountCreateDto.userName;
private static final var error = new Error(errorType, errorMessage);
Validation<Seq<Error>, Account> validateUserIdIsUnique(Account acc) {
return userNameUniquenessChecker.isUnique(acc.userName.text) ?
Validation.valid(userName) :
Validation.invalid(error);
}
You can elide the temporaries - validAcc, validAcc2 & validAccDto, however I left them in for clarity.
(caveat emptor - haven't tested that this code works, or even compiles)
Upvotes: 1