可完成的期货。处理业务的最佳方式是什么 "exceptions"?

Completable futures. What's the best way to handle business "exceptions"?

我刚刚开始熟悉 Java 的 CompletableFuture 工具。我创建了一个小玩具应用程序来模拟一些几乎所有开发人员都会遇到的重复用例。

在这个例子中,我只是想在数据库中保存一个东西,但在这样做之前我想检查一下这个东西是否已经保存。

如果事物已经在数据库中,则流程(完整table 期货链)应该停止并且不保存事物。我正在做的是抛出一个异常,所以最终我可以处理它并向服务的客户提供一个好的消息,这样他就可以知道发生了什么。

这是我目前尝试过的方法:

首先是尝试保存事物的代码,如果事物已经在 table:

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

这是我正在尝试的测试 运行:

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

这种做法是我从这个回复中得到的:(投票最多的答案的第一部分)。

但是,这不起作用。 ThingAlreadyExist 确实被抛出,但它从未被我的 try catch 块处理过。 我的意思是,这个:

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

永远不会执行。

我有 2 个问题,

  1. 有没有更好的方法?

  2. 如果没有,我是不是漏掉了什么?为什么我无法在测试中处理异常?

谢谢!

更新(06-06-2019)

谢谢 VGR,你是对的。这是有效的代码:

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

通过对包含在 Future 中的代码进行单元测试,您正在测试 java 的 Future 框架。您不应该测试库 - 您要么信任它们,要么不信任它们。

相反,测试您的 代码是否在孤立的情况下抛出正确的异常。分解逻辑并进行测试。

您还可以对您的应用进行集成测试,以断言您的整个 应用 行为正确(无论实现如何)。

你必须知道get() and join().

之间的区别

方法get() is inherited from the Future interface and will wrap exceptions in an ExecutionException.

方法join() is specific to CompletableFuture and will wrap exceptions in a CompletionException,是unchecked exception,更适合不声明checked exceptions的函数式接口

话虽这么说, addresses use cases where the function has to do either, return a value or throw an unchecked exception, whereas your use case involves compose, where the function will return a new CompletionStage。这允许替代解决方案,如

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

CompletableFuture.failedFuture 已添加到 Java 9。如果您仍然需要 Java 8 支持,您可以将其添加到您的代码库

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

这使得将来可以轻松迁移到更新的 Java 版本。