流畅地链接多个方法,类似于 Stream 而没有 Optional 类型的冗长

Chaining multiple methods fluently akin to a Stream without the verbosity of the Optional type

在 Spring 应用程序中,我倾向于在控制器方法中获取请求主体,并希望通过多个方法调用(return 沿途使用不同的类型)将其流利地传递给管道,例如以下(简化)示例:

public ResponseEntity<FooDto> postFoo(@RequestBody final FooDto requestBody) {
  return Optional.of(requestBody) // Optional<FooDto>
      .map(mapper::fromDto) // Optional<FooEntity>
      .map(service::insertEntity) // Optional<FooEntity>
      .map(mapper::fromEntity) // Optional<FooDto>
      .map(dto -> ResponseEntity.created(/* ... */).body(dto).build()) // Optional<ResponseEntity<FooDto>>
      .orElseThrow(IllegalStateException::new);
}

如您所见,我很想应用一些 FP 模式,但可选的 class 并不适合这样做,因为隐含的 "optionality" 是人为的,感兴趣的底层对象应该首先永远不要空着。因此,最终的异常不会(希望)被抛出,或者只是调用 Optional::get 也不是一个很好的选择,因为 Sonarlint 抱怨未经检查的 get 调用,这是理所当然的。

是否有任何可用的惯用方法,甚至可能与 vavr 或其他 FP 库结合使用,以比使用此类人工 Optional 构造更好地表达此类方法链?否则我可能不得不避免这样做并恢复到带有十几个变量的 classic 命令式方法。

编辑:如果使用 return Either<ErrorReason, Optional<FooEntity>> 的方法,我尝试使用 Optional 的方式很容易失控,这使得 Optional<Either<ErrorReason, Optional<FooEntity>>> 最终变得不再清晰。

执行您要查找的内容的最简洁方法是恢复命令式样式,例如:

public ResponseEntity<FooDto> postFoo(final FooDto requestBody) {
    final FooEntity fooEntity = fromDto(requestBody);
    final FooEntity updatedEntity = insertEntity(fooEntity); // should be void?
    final FooDto responseDto = fromEntity(updatedEntity);
    return ResponseEntity.created(/* ... */)
            .body(responseDto)
            .build();
}

我同意 Naman 的观点,在这种情况下命令式风格可能是最简洁的方式。

如果你真的很想做一些像流一样的Optional风格你可以创建你自己的class

public final class Value<T> {
  private final T value;

  // Private constructor to force usage of static construction
  private Value(T value) {
    this.value = value;
  }

  // Static constructor Optional style
  public static <T> Value<T> of(T value) {
    return new Value<>(value);
  }

  public <R> Value<R> map(Function<? super T, ? extends R> mapper) {
    return new Value<>(mapper.apply(this.value));
  }

  // method to unwrap value
  public T get() {
    return value;
  }
}

然后你会像

那样使用它
public ResponseEntity<FooDto> postFoo(@RequestBody final FooDto requestBody) {
  return Value.of(requestBody) // Value<FooDto>
      .map(mapper::fromDto) // Value<FooEntity>
      .map(service::insertEntity) // Value<FooEntity>
      .map(mapper::fromEntity) // Value<FooDto>
      .map(dto -> ResponseEntity.created(/* ... */).body(dto).build()) // Value<ResponseEntity<FooDto>>
      .get();
}

同样,我强烈反对这种解决方案,我会选择使用命令式风格。