I have the following process I’d like to describe with chained methods returning Either
.
The process receives a context object containing a WhateverInput
and correlationId
and processes these in the following steps:
- validation of the WhateverInput
- mapping the WhateverInput to WhateverEntity
- saving the WhateverEntity in a storage
- mapping the returned WhateverEntity (having Id at this point) to WhateverResult
The correlationId
is there to mark which process the operation belongs to if the process fails.
What I tried is map
and flatMap
, but both of them expects a function where the input is the Either’s Right and an Either as a return value. However my method signatures look like below due to that I don’t want to put the correlation id into a field.
public class VavrPractice {
public static void handle(Context context) {
var result = Either.right(context)
.map(validateInput(context.getWhateverEntityInput(), context.getCorrelationId())) // shows error
}
private static Either<ErrorResult, WhateverEntityInput> validateInput(WhateverEntityInput whateverEntityInput, UUID correlationId) {}
private static Either<ErrorResult, WhateverEntity> mapToWhateverEntity(WhateverEntityInput whateverEntityInput, UUID correlationId) {}
private static Either<ErrorResult, WhateverEntity> saveToDatabase(WhateverEntityInput whateverEntityInput, UUID correlationId) {}
private static Either<ErrorResult, WhateverEntityResult> mapToWhateverEntityResult(WhateverEntity whateverEntity, UUID correlationId) {}
}
How to do this in a nice, best/good practices way? What I can think of is creating a Context
object adding to it both the WhateverInput
and correlationId
and this Context
will be the Right
side of the Either
and moving this object between methods. But, it feels a bit clunky, heavy.
I study functional programming in both Java and C# in the same time and the example above can be done in C# using Bind
and I am looking for an equivalent for that or the solution how the Bind
, what it seems like map
or flatMap
, can be done.
FYI, this exact question is asked in the vavr repo on GitHub.
7
If you fix your types a little you can do
public static void handle(ContextX context) {
UUID correlationId = context.getCorrelationId();
var result = Either.<ErrorResult, WhateverEntityInput>right(context.getWhateverEntityInput())
.flatMap(i -> validateInput(i, correlationId))
.flatMap(i -> mapToWhateverEntity(i, correlationId))
.flatMap(e -> saveToDatabase(e, correlationId))
.flatMap(e -> mapToWhateverEntityResult(e, correlationId));
}
1
Have you considered using Result
instead of Either
?
public class Result<T> {
private final boolean success;
private T value; // only populated when success = true
private List<String> errors; // only populated when success = false
private Result<T>(boolean success, T value, List<String> errors) {
this.success = success;
this.value = value;
this.errors = errors;
}
public static <T> Result<T> success(T value) {
return new Result<>(true, value, null);
}
public static <T> Result<T> error(List<String> errors) {
return new Result<>(false, null, errors);
}
public static <T> Result<T> error(String error) {
return new Result<>(false, null, List.of(error));
}
public <T2> Result<T2> map(Function<T, T2> mapper) {
return success ? Result.success(mapper.apply(value)) : this;
}
public T orElse(T errValue) {
return success ? value : errValue;
}
public boolean isSuccess() {
return success;
}
public boolean isError() {
return !success;
}
public T getValue() {
if (!success) throw new IllegalStateException();
return value;
}
public List<String> getErrors() {
if (success) throw new IllegalStateException();
return errors;
}
}
public class MyClass {
public String doLotsOfStuff() {
String value = "v1";
Result<String> result = doStuff1(value)
.map(this::doStuff2)
.map(this::doStuff3);
if (result.isError()) {
System.err.println(result.getErrors().toString());
}
return result.orElse("error");
}
private Result<String> doStuff1(String inValue) {
if (!inValue.startsWith("foo")) {
return Result.error("inValue does not start with 'foo'");
}
return Result.success("bar");
}
private Result<String> doStuff2(String inValue) { ... }
private Result<String> doStuff3(String inValue) { ... }
}
3