Цепочка нескольких методов свободно похожа на Stream без многословия типа Optional - PullRequest
1 голос
/ 14 марта 2020

В приложении Spring я стремлюсь получить тело запроса в методе контроллера и хочу быстро передать его через несколько вызовов методов (по пути возвращая различные типы), например, в следующем (упрощенном) примере:

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, но класс Optional на самом деле не подходит для этого, поскольку подразумеваемая "необязательность" является искусственной, и основной объект интереса никогда не должен быть пустым в первом место. Таким образом, окончательное исключение (надеюсь) никогда не будет выдано, и в качестве альтернативы просто вызов Optional::get не очень хороший вариант, так как Sonarlint жалуется на непроверенный вызов get и вполне справедливо.

Есть ли идиоматизм c способ, возможно, даже в сочетании с vavr или другими библиотеками FP, для express такой цепочки методов лучше, чем с такими искусственными необязательными конструкциями? В противном случае мне, возможно, придется воздержаться от этого и вернуться к классическому c императивному подходу с дюжиной переменных.

РЕДАКТИРОВАТЬ: способ, которым я пытаюсь использовать Optional, легко выходит из-под контроля при работе с методами это возвращение Either<ErrorReason, Optional<FooEntity>>, что делает Optional<Either<ErrorReason, Optional<FooEntity>>> более не читаемым.

Ответы [ 2 ]

0 голосов
/ 20 марта 2020

Я согласен с Наманом, что императивный стиль, возможно, является самым чистым способом в этом случае.

Если вы действительно хотите использовать стиль Optional, такой как flow, вы можете создать свой собственный класс

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();
}

Опять же, я крайне не рекомендую это решение, и я бы просто выбрал использование императивного стиля.

0 голосов
/ 14 марта 2020

Самый чистый способ выполнить то, что вы ищете, это восстановить императивный стиль, такой как:

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();
}
...