Project Reactor - 使用 defer() 使方法可重试

Project Reactor - the usage of defer() to make method retry-able

最近我试图通过在 return 成功 mono.just 之前设置对 return 多个 Mono.error() 的模拟依赖来测试单元测试中的重试行为() 最后:

@Mock
Dependency dependency;

@InjectMocks
ClassUnderTest classUnderTest;

@Test
void someTest() {
    final Object object = new Object();
    when(dependency.method(anyString()))
        .thenReturn(Mono.error(new Exception()))
        .thenReturn(Mono.error(new Exception()))
        .thenReturn(Mono.error(new Exception()))
        .thenReturn(Mono.just(object));

    StepVerifier.create(classUnderTest.method("abc"))
        .expectNext(object)
        .verifyComplete();

    verify(dependency, times(4)).method("abc");
}

上面的设置是行不通的,后来我发现,Reactor中的重试不是通过调用特定时间的方法来完成的,而是调用一次方法,获取发布者,然后重新订阅一次又一次。

class ClassUnderTest {
    private Dependency dependency;

    public Mono<Object> method(final String str) {
        return this.dependency.method(str).retryWhen(Retry.max(3));
    }
}

并且重新订阅 将不会 工作,如果 Dependency#method 实现为:

class Dependency {
    private OtherDependency otherDependency;

    public Mono<Object> method(final String str) {
        return this.otherDependency.get(str).map(/* some mapping logic */);
    }
}

Dependency#method不能对OtherDependency#get是否延期做太多假设。因此,Dependency 需要:

class Dependency {
    private OtherDependency otherDependency;

    public Mono<Object> method(final String str) {
        return Mono.defer(() -> this.otherDependency.get(str)).map(/* some mapping logic */);
    }
}

既然我们想说每个方法都应该是"retry-able",那是否意味着我们需要总是使用defer(...)?

还是我理解有误?

早该考虑一下

更简单的方法是在附加 retryWhen 运算符之前用 defer 包装发布者,而不是使所有方法都是本机 "retry-able"。

之前:

class ClassUnderTest {
    private Dependency dependency;

    public Mono<Object> method(final String str) {
        return dependency.method(str).retryWhen(Retry.max(3));
    }
}

之后:

class ClassUnderTest {
    private Dependency dependency;

    public Mono<Object> method(final String str) {
        return Mono.defer(() -> dependency.method(str)).retryWhen(Retry.max(3));
    }
}

现在,我们不必说所有方法都应该是原生的 "retry-able",而是要始终用 defer.

包装要重试的发布者