futuros completável. Qual é a melhor maneira de lidar com "exceções" de negócios?

Yuliban:

Eu estou apenas começando a se familiarizar com a ferramenta CompletableFuture de Java. Eu criei um aplicativo de brinquedo para modelar algum caso de uso recorrente quase qualquer dev iria enfrentar.

Neste exemplo, eu simplesmente quero salvar uma coisa em um DB, mas antes de fazer isso eu quero verificar se a coisa já estava salvo.

Se a coisa já está no DB o fluxo (a cadeia de futuros completável) deve parar e não salvar a coisa. O que estou fazendo é lançar uma exceção para que, eventualmente, eu posso lidar com isso e dar uma boa mensagem para o cliente do serviço para que ele possa saber o que aconteceu.

Isto é o que eu tentei até agora:

Primeiro, o código que tentar salvar a coisa ou lançar um erro se a coisa já está na tabela:

repository
        .query(thing.getId())
        .thenCompose(
            mayBeThing -> {
              if (mayBeThing.isDefined()) throw new CompletionException(new ThingAlreadyExists());
              else return repository.insert(new ThingDTO(thing.getId(), thing.getName()));

E este é o teste que eu estou tentando executar:

    CompletableFuture<Integer> eventuallyMayBeThing =
        service.save(thing).thenCompose(i -> service.save(thing));
    try {
      eventuallyMayBeThing.get();
    } catch (CompletionException ce) {
      System.out.println("Completion exception " + ce.getMessage());
      try {
        throw ce.getCause();
      } catch (ThingAlreadyExist tae) {
        assert (true);
      } catch (Throwable t) {
        throw new AssertionError(t);
      }
    }

Esta maneira de fazê-lo a tirei de esta resposta: lançando exceção de CompletableFuture (a primeira parte do mais votado resposta).

No entanto, isso não está funcionando. O ThingAlreadyExistestá sendo jogado na verdade, mas nunca sendo tratado pelo meu bloco try catch. Eu queria dizer isso:

catch (CompletionException ce) {
      System.out.println("Completion exception " + ce.getMessage());
      try {
        throw ce.getCause();
      } catch (ThingAlreadyExist tae) {
        assert (true);
      } catch (Throwable t) {
        throw new AssertionError(t);
      }

nunca é executado.

Eu tenho 2 perguntas,

  1. Existe uma maneira melhor?

  2. Se não, estou faltando alguma coisa? Por que não pode tratar a exceção no meu teste?

Obrigado!

Atualização (06-06-2019)

Graças VGR você está certo. Este é o trabalho de código:

try {
      eventuallyMayBeThing.get();
    } catch (ExecutionException ce) {
      assertThat(ce.getCause(), instanceOf(ThingAlreadyExists.class));
    }
Holger:

Você tem que estar ciente das diferenças entre get()e join().

O método get()é herdado da Futureinterface do e vai embrulhar exceções em um ExecutionException.

O método join()é específico para CompletableFuturee vai embrulhar exceções em um CompletionException, que é uma exceção não verificada, o que o torna mais adequado para as interfaces funcionais que não declaram exceções verificadas.

Dito isto, as respostas ligadas endereços usar casos em que a função tem de fazer qualquer um, retornar um valor ou lançar uma exceção não verificada, enquanto seu caso de uso envolve compose, onde a função retornará um novo CompletionStage. Isso permite uma solução alternativa como

.thenCompose(mayBeThing -> mayBeThing.isDefined()?
    CompletableFuture.failedFuture​(new ThingAlreadyExists()):
    repository.insert(new ThingDTO(thing.getId(), thing.getName())))

CompletableFuture.failedFuture foi adicionado em Java 9. Se você ainda precisa de suporte a Java 8, você pode adicioná-lo à sua base de código

public static <T> CompletableFuture<T> failedFuture(Throwable t) {
    final CompletableFuture<T> cf = new CompletableFuture<>();
    cf.completeExceptionally(t);
    return cf;
}

o que permite uma fácil migração para uma nova versão Java no futuro.

Acho que você gosta

Origin http://43.154.161.224:23101/article/api/json?id=201900&siteId=1
Recomendado
Clasificación