Los futuros completable. ¿Cuál es la mejor manera de manejar las "excepciones" de negocios?

Yuliban:

Estoy empezando a familiarizarse con la herramienta CompletableFuture desde Java. He creado una pequeña aplicación de juguetes para modelar algunos casos de uso recurrente casi cualquier dev se enfrentaría.

En este ejemplo simplemente quiero guardar una cosa en una base de datos, pero antes de hacerlo quiero comprobar si la cosa ya se salvó.

Si la cosa ya está en la base de datos del flujo (la cadena de los futuros completable) debe parar y no guarda la cosa. Lo que estoy haciendo es lanzar una excepción por lo que finalmente puedo manejarlo y dar un buen mensaje al cliente del servicio para que pueda saber lo que pasó.

Esto es lo que he probado hasta ahora:

En primer lugar el código que tratar de salvar la cosa o un error si la cosa ya está en la tabla:

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

Y esta es la prueba de que estoy tratando de ejecución:

    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 forma de hacer que me tomó de esta respuesta: Lanzar excepción de CompletableFuture (la primera parte de la respuesta más votada).

Sin embargo, esto no está funcionando. El ThingAlreadyExistse está lanzando de hecho pero nunca es manejada por mi bloque intento de captura. Quiero decir esto:

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 se ejecuta.

Tengo 2 preguntas,

  1. ¿Hay una mejor manera?

  2. Si no, me estoy perdiendo algo? Por qué no puedo manejar la excepción en mi prueba?

¡Gracias!

Actualización (06-06-2019)

Gracias VGR que tiene razón. Este es el trabajo código:

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

Usted tiene que ser consciente de las diferencias entre get()y join().

El método get()se hereda de la Futureinterfaz y se envolverá excepciones en una ExecutionException.

El método join()es específico para CompletableFuturey se ajustará excepciones en una CompletionException, que es una excepción sin control, que hace que sea más adecuado para las interfaces funcionales que no declaran excepciones controladas.

Una vez dicho esto, las respuestas vinculadas direcciones utilizan casos en los que la función tiene que hacer bien, devolver un valor o una excepción sin control, mientras que el caso de uso implica compose, en donde la función devolverá una nueva CompletionStage. Esto permite una solución alternativa como

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

CompletableFuture.failedFuture se ha agregado en Java 9. Si aún necesita Java 8 de soporte, puede agregarlo a la base de código

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

que permite una fácil migración a una nueva versión de Java en el futuro.

Supongo que te gusta

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