Skip to content

Commit 462960f

Browse files
RomehRobWin
authored andcommitted
AsyncRetry merge into Retry plus spring Retry AOP updates (ReactiveX#387)
* deprecating async retry and merge it with retry * add async retry unit test * fix the java doc
1 parent 23529e8 commit 462960f

File tree

35 files changed

+1869
-2288
lines changed

35 files changed

+1869
-2288
lines changed

resilience4j-all/src/main/java/io/github/resilience4j/decorators/Decorators.java

Lines changed: 336 additions & 324 deletions
Large diffs are not rendered by default.

resilience4j-all/src/test/java/io/github/resilience4j/decorators/DecoratorsTest.java

Lines changed: 89 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,24 @@
1818
*/
1919
package io.github.resilience4j.decorators;
2020

21+
import static org.assertj.core.api.Assertions.assertThat;
22+
import static org.mockito.BDDMockito.given;
23+
import static org.mockito.Mockito.mock;
24+
import static org.mockito.Mockito.times;
25+
26+
import java.io.IOException;
27+
import java.time.Duration;
28+
import java.util.concurrent.CompletableFuture;
29+
import java.util.concurrent.CompletionStage;
30+
import java.util.concurrent.ExecutionException;
31+
import java.util.concurrent.Executors;
32+
import java.util.function.Function;
33+
import java.util.function.Supplier;
34+
35+
import org.junit.Before;
36+
import org.junit.Test;
37+
import org.mockito.BDDMockito;
38+
2139
import io.github.resilience4j.bulkhead.Bulkhead;
2240
import io.github.resilience4j.cache.Cache;
2341
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
@@ -31,23 +49,6 @@
3149
import io.vavr.CheckedFunction1;
3250
import io.vavr.CheckedRunnable;
3351
import io.vavr.control.Try;
34-
import org.junit.Before;
35-
import org.junit.Test;
36-
import org.mockito.BDDMockito;
37-
38-
import java.io.IOException;
39-
import java.time.Duration;
40-
import java.util.concurrent.CompletableFuture;
41-
import java.util.concurrent.CompletionStage;
42-
import java.util.concurrent.ExecutionException;
43-
import java.util.concurrent.Executors;
44-
import java.util.function.Function;
45-
import java.util.function.Supplier;
46-
47-
import static org.assertj.core.api.Assertions.assertThat;
48-
import static org.mockito.BDDMockito.given;
49-
import static org.mockito.Mockito.mock;
50-
import static org.mockito.Mockito.times;
5152

5253
public class DecoratorsTest {
5354
public boolean state = false;
@@ -65,11 +66,11 @@ public void testDecorateSupplier() {
6566
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("helloBackend");
6667

6768
Supplier<String> decoratedSupplier = Decorators.ofSupplier(() -> helloWorldService.returnHelloWorld())
68-
.withCircuitBreaker(circuitBreaker)
69-
.withRetry(Retry.ofDefaults("id"))
70-
.withRateLimiter(RateLimiter.ofDefaults("testName"))
71-
.withBulkhead(Bulkhead.ofDefaults("testName"))
72-
.decorate();
69+
.withCircuitBreaker(circuitBreaker)
70+
.withRetry(Retry.ofDefaults("id"))
71+
.withRateLimiter(RateLimiter.ofDefaults("testName"))
72+
.withBulkhead(Bulkhead.ofDefaults("testName"))
73+
.decorate();
7374

7475
String result = decoratedSupplier.get();
7576
assertThat(result).isEqualTo("Hello world");
@@ -88,11 +89,11 @@ public void testDecorateCheckedSupplier() throws IOException {
8889
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("helloBackend");
8990

9091
CheckedFunction0<String> decoratedSupplier = Decorators.ofCheckedSupplier(() -> helloWorldService.returnHelloWorldWithException())
91-
.withCircuitBreaker(circuitBreaker)
92-
.withRetry(Retry.ofDefaults("id"))
93-
.withRateLimiter(RateLimiter.ofDefaults("testName"))
94-
.withBulkhead(Bulkhead.ofDefaults("testName"))
95-
.decorate();
92+
.withCircuitBreaker(circuitBreaker)
93+
.withRetry(Retry.ofDefaults("id"))
94+
.withRateLimiter(RateLimiter.ofDefaults("testName"))
95+
.withBulkhead(Bulkhead.ofDefaults("testName"))
96+
.decorate();
9697

9798
String result = Try.of(decoratedSupplier).get();
9899
assertThat(result).isEqualTo("Hello world");
@@ -109,11 +110,11 @@ public void testDecorateRunnable() {
109110
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("helloBackend");
110111

111112
Runnable decoratedRunnable = Decorators.ofRunnable(() -> helloWorldService.sayHelloWorld())
112-
.withCircuitBreaker(circuitBreaker)
113-
.withRetry(Retry.ofDefaults("id"))
114-
.withRateLimiter(RateLimiter.ofDefaults("testName"))
115-
.withBulkhead(Bulkhead.ofDefaults("testName"))
116-
.decorate();
113+
.withCircuitBreaker(circuitBreaker)
114+
.withRetry(Retry.ofDefaults("id"))
115+
.withRateLimiter(RateLimiter.ofDefaults("testName"))
116+
.withBulkhead(Bulkhead.ofDefaults("testName"))
117+
.decorate();
117118

118119
decoratedRunnable.run();
119120

@@ -130,11 +131,11 @@ public void testDecorateCheckedRunnable() throws IOException {
130131
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("helloBackend");
131132

132133
CheckedRunnable decoratedRunnable = Decorators.ofCheckedRunnable(() -> helloWorldService.sayHelloWorldWithException())
133-
.withCircuitBreaker(circuitBreaker)
134-
.withRetry(Retry.ofDefaults("id"))
135-
.withRateLimiter(RateLimiter.ofDefaults("testName"))
136-
.withBulkhead(Bulkhead.ofDefaults("testName"))
137-
.decorate();
134+
.withCircuitBreaker(circuitBreaker)
135+
.withRetry(Retry.ofDefaults("id"))
136+
.withRateLimiter(RateLimiter.ofDefaults("testName"))
137+
.withBulkhead(Bulkhead.ofDefaults("testName"))
138+
.decorate();
138139

139140
Try.run(decoratedRunnable);
140141

@@ -172,6 +173,32 @@ public void testDecorateCompletionStage() throws ExecutionException, Interrupted
172173
BDDMockito.then(helloWorldService).should(times(1)).returnHelloWorld();
173174
}
174175

176+
@Test
177+
public void testDecorateCompletionStageNewAPI() throws ExecutionException, InterruptedException {
178+
// Given the HelloWorldService returns Hello world
179+
given(helloWorldService.returnHelloWorld()).willReturn("Hello world");
180+
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("helloBackend");
181+
182+
Supplier<CompletionStage<String>> completionStageSupplier =
183+
() -> CompletableFuture.supplyAsync(helloWorldService::returnHelloWorld);
184+
185+
CompletionStage<String> completionStage = Decorators.ofCompletionStage(completionStageSupplier)
186+
.withCircuitBreaker(circuitBreaker)
187+
.withRetry(Retry.ofDefaults("id"), Executors.newSingleThreadScheduledExecutor())
188+
.withBulkhead(Bulkhead.ofDefaults("testName"))
189+
.get();
190+
191+
String value = completionStage.toCompletableFuture().get();
192+
assertThat(value).isEqualTo("Hello world");
193+
194+
CircuitBreaker.Metrics metrics = circuitBreaker.getMetrics();
195+
assertThat(metrics.getNumberOfBufferedCalls()).isEqualTo(1);
196+
assertThat(metrics.getNumberOfSuccessfulCalls()).isEqualTo(1);
197+
198+
// Then the helloWorldService should be invoked 1 time
199+
BDDMockito.then(helloWorldService).should(times(1)).returnHelloWorld();
200+
}
201+
175202
@Test
176203
public void testExecuteConsumer() throws ExecutionException, InterruptedException {
177204
// Given the HelloWorldService returns Hello world
@@ -199,11 +226,11 @@ public void testDecorateFunction() {
199226
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("helloBackend");
200227

201228
Function<String, String> decoratedFunction = Decorators.ofFunction(helloWorldService::returnHelloWorldWithName)
202-
.withCircuitBreaker(circuitBreaker)
203-
.withRetry(Retry.ofDefaults("id"))
204-
.withRateLimiter(RateLimiter.ofDefaults("testName"))
205-
.withBulkhead(Bulkhead.ofDefaults("testName"))
206-
.decorate();
229+
.withCircuitBreaker(circuitBreaker)
230+
.withRetry(Retry.ofDefaults("id"))
231+
.withRateLimiter(RateLimiter.ofDefaults("testName"))
232+
.withBulkhead(Bulkhead.ofDefaults("testName"))
233+
.decorate();
207234

208235
String result = decoratedFunction.apply("Name");
209236
assertThat(result).isEqualTo("Hello world Name");
@@ -220,11 +247,11 @@ public void testDecorateCheckedFunction() throws IOException {
220247
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("helloBackend");
221248

222249
CheckedFunction1<String, String> decoratedFunction = Decorators.ofCheckedFunction(helloWorldService::returnHelloWorldWithNameWithException)
223-
.withCircuitBreaker(circuitBreaker)
224-
.withRetry(Retry.ofDefaults("id"))
225-
.withRateLimiter(RateLimiter.ofDefaults("testName"))
226-
.withBulkhead(Bulkhead.ofDefaults("testName"))
227-
.decorate();
250+
.withCircuitBreaker(circuitBreaker)
251+
.withRetry(Retry.ofDefaults("id"))
252+
.withRateLimiter(RateLimiter.ofDefaults("testName"))
253+
.withBulkhead(Bulkhead.ofDefaults("testName"))
254+
.decorate();
228255

229256
String result = Try.of(() -> decoratedFunction.apply("Name")).get();
230257
assertThat(result).isEqualTo("Hello world Name");
@@ -242,10 +269,10 @@ public void testDecoratorBuilderWithRetry() {
242269
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("helloBackend");
243270

244271
Supplier<String> decoratedSupplier = Decorators.ofSupplier(() -> helloWorldService.returnHelloWorld())
245-
.withCircuitBreaker(circuitBreaker)
246-
.withRetry(Retry.ofDefaults("id"))
247-
.withBulkhead(Bulkhead.ofDefaults("testName"))
248-
.decorate();
272+
.withCircuitBreaker(circuitBreaker)
273+
.withRetry(Retry.ofDefaults("id"))
274+
.withBulkhead(Bulkhead.ofDefaults("testName"))
275+
.decorate();
249276

250277
Try.of(decoratedSupplier::get);
251278

@@ -257,23 +284,23 @@ public void testDecoratorBuilderWithRetry() {
257284
}
258285

259286
@Test
260-
public void testDecoratorBuilderWithRateLimiter(){
287+
public void testDecoratorBuilderWithRateLimiter() {
261288
// Given the HelloWorldService returns Hello world
262289
given(helloWorldService.returnHelloWorld()).willReturn("Hello world");
263290

264291
// Create a custom RateLimiter configuration
265292
RateLimiterConfig config = RateLimiterConfig.custom()
266-
.timeoutDuration(Duration.ofMillis(100))
267-
.limitRefreshPeriod(Duration.ofSeconds(1))
268-
.limitForPeriod(1)
269-
.build();
293+
.timeoutDuration(Duration.ofMillis(100))
294+
.limitRefreshPeriod(Duration.ofSeconds(1))
295+
.limitForPeriod(1)
296+
.build();
270297

271298
// Create a RateLimiter
272299
RateLimiter rateLimiter = RateLimiter.of("backendName", config);
273300

274301
CheckedFunction0<String> restrictedSupplier = Decorators.ofCheckedSupplier(() -> helloWorldService.returnHelloWorld())
275-
.withRateLimiter(rateLimiter)
276-
.decorate();
302+
.withRateLimiter(rateLimiter)
303+
.decorate();
277304

278305
alignTime(rateLimiter);
279306
Try<String> firstTry = Try.of(restrictedSupplier);
@@ -308,8 +335,8 @@ public void testDecorateCheckedSupplierWithCache() {
308335
given(cache.get("testKey")).willReturn("Hello from cache");
309336

310337
CheckedFunction1<String, String> cachedFunction = Decorators.ofCheckedSupplier(() -> "Hello world")
311-
.withCache(Cache.of(cache))
312-
.decorate();
338+
.withCache(Cache.of(cache))
339+
.decorate();
313340
String value = Try.of(() -> cachedFunction.apply("testKey")).get();
314341
assertThat(value).isEqualTo("Hello from cache");
315342
}
@@ -325,8 +352,8 @@ public void testDecorateSupplierWithCache() {
325352
given(cache.get("testKey")).willReturn("Hello from cache");
326353

327354
Function<String, String> cachedFunction = Decorators.ofSupplier(() -> "Hello world")
328-
.withCache(Cache.of(cache))
329-
.decorate();
355+
.withCache(Cache.of(cache))
356+
.decorate();
330357
String value = cachedFunction.apply("testKey");
331358
assertThat(value).isEqualTo("Hello from cache");
332359
}

resilience4j-annotations/src/main/java/io/github/resilience4j/retry/annotation/AsyncRetry.java

Lines changed: 0 additions & 41 deletions
This file was deleted.

resilience4j-documentation/src/docs/asciidoc/addon_guides/springboot.adoc

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,7 @@ For example:
3333
"resilience4j.retry.backendA.failedCallsWithoutRetry",
3434
"resilience4j.retry.backendA.failedCallsWithRetry",
3535
"resilience4j.retry.backendA.successCalls",
36-
"resilience4j.retry.backendA.successCallsWithRetry",
37-
"resilience4j.asyncRetry.backendA.failedCallsWithoutRetry",
38-
"resilience4j.asyncRetry.backendB.failedCallsWithRetry",
39-
"resilience4j.asyncRetry.backendB.successCalls",
40-
"resilience4j.asyncRetry.backendB.successCallsWithRetry"
36+
"resilience4j.retry.backendA.successCallsWithRetry"
4137
]
4238
}
4339
----
@@ -188,9 +184,7 @@ The rules for Retry configuration :
188184

189185
The rules for Retry spring annotation usage :
190186

191-
- You can use the same back-end configuration for both sync and async retry if you use both annotations in for the same backed method level wise only ,
192-
if you mix annotations between class level and method level on the same back-end class , validation exception will be thrown
193-
- For `AsyncRetry` annotation , please make sure the return type is instance of Java `CompletionStage` otherwise runtime exception will be thrown
187+
- Retry aspect will detect the proper handling based into the method return type for synchronous and asynchronous execution
194188

195189
Code example of retry and async retry annotation usage in Java Spring component :
196190
[source,java]
@@ -206,7 +200,7 @@ public void doSomething(boolean throwBackendTrouble) throws IOException {
206200
}
207201
}
208202
209-
@AsyncRetry(name = RetryDummyService.BACKEND)
203+
@Retry(name = RetryDummyService.BACKEND)
210204
@Override
211205
public CompletionStage<String> doSomethingAsync(boolean throwException) throws IOException {
212206
if (throwException) {

resilience4j-documentation/src/docs/asciidoc/addon_guides/springboot2.adoc

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,7 @@ For example:
3333
"resilience4j.retry.backendA.failedCallsWithoutRetry",
3434
"resilience4j.retry.backendA.failedCallsWithRetry",
3535
"resilience4j.retry.backendA.successCalls",
36-
"resilience4j.retry.backendA.successCallsWithRetry",
37-
"resilience4j.asyncRetry.backendA.failedCallsWithoutRetry",
38-
"resilience4j.asyncRetry.backendB.failedCallsWithRetry",
39-
"resilience4j.asyncRetry.backendB.successCalls",
40-
"resilience4j.asyncRetry.backendB.successCallsWithRetry"
36+
"resilience4j.retry.backendA.successCallsWithRetry"
4137
]
4238
}
4339
----
@@ -182,16 +178,13 @@ resilience4j.retry:
182178
----
183179
The rules for Retry configuration :
184180

185-
- By default the same back end configuration will be used for sync and async retry configuration if not defined otherwise.
186181
- enableRandomizedWait and enableExponentialBackoff is false by default.
187182
- You can not enable both enableRandomizedWait and enableExponentialBackoff , validation exception will be thrown if it happen.
188183
- If exponentialBackoffMultiplier is not provided if enableExponentialBackoff is enabled , default ExponentialBackoff will be used , same story for enableRandomizedWait.
189184

190185
The rules for Retry spring annotation usage :
191186

192-
- You can use the same back-end configuration for both sync and async retry if you use both annotations in for the same backed method level wise only ,
193-
if you mix annotations between class level and method level on the same back-end class , validation exception will be thrown
194-
- For `AsyncRetry` annotation , please make sure the return type is instance of Java `CompletionStage` otherwise runtime exception will be thrown
187+
- Retry aspect will detect the proper handling based into the method return type for synchronous and asynchronous execution
195188

196189
Code example of retry and async retry annotation usage in Java Spring component :
197190
[source,java]
@@ -207,7 +200,7 @@ public void doSomething(boolean throwBackendTrouble) throws IOException {
207200
}
208201
}
209202
210-
@AsyncRetry(name = RetryDummyService.BACKEND)
203+
@Retry(name = RetryDummyService.BACKEND)
211204
@Override
212205
public CompletionStage<String> doSomethingAsync(boolean throwException) throws IOException {
213206
if (throwException) {

0 commit comments

Comments
 (0)