可完成的期货。处理业务“异常”的最佳方法是什么?

时间:2019-06-05 23:48:20

标签: java completable-future

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

在此示例中,我只想将一个事物保存在数据库中,但是在此之前,我想检查该事物是否已经保存。

如果事物已经在数据库中,则流程(可完成的期货链)应该停止而不保存事物。我正在做的事情是引发异常,因此最终我可以处理该异常,并向服务的客户传达良好的信息,以便他可以知道发生了什么。

这是我到目前为止尝试过的:

首先,尝试保存事物或如果事物已在表中而引发错误的代码:

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

这种方式是我从以下回复中获得的:Throwing exception from CompletableFuture(投票最多的答案的第一部分)。

但是,这不起作用。 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));
    }

2 个答案:

答案 0 :(得分:3)

通过单元测试包装在Future中的代码,您正在测试Java的Future框架。您不应该测试库-您可以信任它们,也可以不信任它们。

相反,测试您的 代码是否应单独抛出正确的异常。打破逻辑并进行测试。

您还可以集成测试您的应用程序,以断言整个 app 的行为正确(与实现无关)。

答案 1 :(得分:2)

您必须了解get()join()之间的区别。

方法get()继承自the Future interface,并将异常包装在ExecutionException中。

方法join()特定于CompletableFuture,并将异常包装在CompletionException中,这是一个未经检查的异常,这使其更适合于未声明已检查的功能接口例外。

话虽这么说,linked answer解决了函数必须执行,返回值或引发未经检查的异常的用例,而您的用例涉及了compose,其中函数将返回一个新的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版本。