diff --git a/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/SpringbootResilience4jApplication.java b/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/SpringbootResilience4jApplication.java index b0faf50..0dcf7c0 100644 --- a/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/SpringbootResilience4jApplication.java +++ b/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/SpringbootResilience4jApplication.java @@ -14,6 +14,9 @@ public class SpringbootResilience4jApplication { @Autowired private RateLimiterExamplesRunner rateLimiterExamplesRunner; + @Autowired + private TimeLimiterExamplesRunner timeLimiterExamplesRunner; + public static void main(String[] args) { SpringApplication.run(SpringbootResilience4jApplication.class, args); } @@ -22,5 +25,6 @@ public class SpringbootResilience4jApplication { public void runExamples() { retryExamplesRunner.run(); rateLimiterExamplesRunner.run(); + timeLimiterExamplesRunner.run(); } } \ No newline at end of file diff --git a/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/TimeLimiterExamplesRunner.java b/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/TimeLimiterExamplesRunner.java new file mode 100644 index 0000000..de4412f --- /dev/null +++ b/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/TimeLimiterExamplesRunner.java @@ -0,0 +1,142 @@ +package io.reflectoring.resilience4j.springboot; + +import io.reflectoring.resilience4j.springboot.model.Flight; +import io.reflectoring.resilience4j.springboot.model.SearchRequest; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class TimeLimiterExamplesRunner { + + @Autowired + private TimeLimitingService service; + + private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss SSS"); + + public static void main(String[] args) { + TimeLimiterExamplesRunner runner = new TimeLimiterExamplesRunner(); + runner.run(); + } + + static void delay(int seconds) { + // sleep to simulate delay + try { + Thread.sleep(seconds * 1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + public void run() { + System.out.println("Running timelimiter examples"); + + System.out.println( + "----------------------- basicExample ----------------------------------------------------"); + basicExample(); + + delay(2); // delay just to let the above async operation to complete + + System.out.println( + "----------------------------------------------------------------------------------------------------"); + + System.out.println("----------------------- timeoutExample ----------------------------------------------"); + timeoutExample(); + + delay(2); // delay just to let the above async operation to complete + + System.out.println("----------------------------------------------------------------------------------------------------"); + + System.out.println("----------------------- fallbackExample ----------------------------------------------"); + fallbackExample(); + + delay(2); // delay just to let the above async operation to complete + + System.out.println("----------------------------------------------------------------------------------------------------"); + + System.out.println( + "----------------------- eventsExample ----------------------------------------------------"); + eventsExample(); + delay(10); // delay just to let the above async operation to complete + System.out.println( + "----------------------------------------------------------------------------------------------------"); + } + + private void eventsExample() { + SearchRequest request = new SearchRequest("NYC", "LAX", "10/30/2021"); + for (int i = 0; i < 10; i++) { + int attempt = i; + service.eventsExample(request) + .whenComplete((r, t) -> { + if (t != null) { + System.out.println("Error occurred on search " + attempt + ": " + t.getMessage()); + } + if (r != null) { + System.out + .println("Search " + attempt + " successful, found " + r.size() + " flights"); + } + }); + } + } + + private void timeoutExample() { + SearchRequest request = new SearchRequest("NYC", "LAX", "10/30/2021"); + System.out.println("Calling search; current thread = " + Thread.currentThread().getName()); + CompletableFuture> results = service.timeoutExample(request); + results.whenComplete((result, ex) -> { + if (ex != null) { + System.out.println("Exception " + + ex.getMessage() + + " on thread " + + Thread.currentThread().getName() + + " at " + + LocalDateTime.now().format(formatter)); + ex.printStackTrace(); + } + if (result != null) { + System.out.println(result + " on thread " + Thread.currentThread().getName()); + } + }); + } + + private void basicExample() { + SearchRequest request = new SearchRequest("NYC", "LAX", "10/30/2021"); + System.out.println("Calling search; current thread = " + Thread.currentThread().getName()); + CompletableFuture> results = service.basicExample(request); + results.whenComplete((result, ex) -> { + if (ex != null) { + System.out.println("Exception " + + ex.getMessage() + + " on thread " + + Thread.currentThread().getName() + + " at " + + LocalDateTime.now().format(formatter)); + } + if (result != null) { + System.out.println(result + " on thread " + Thread.currentThread().getName()); + } + }); + } + + private void fallbackExample() { + SearchRequest request = new SearchRequest("NYC", "LAX", "10/30/2021"); + System.out.println("Calling search; current thread = " + Thread.currentThread().getName()); + CompletableFuture> results = service.fallbackExample(request); + results.whenComplete((result, ex) -> { + if (ex != null) { + System.out.println("Exception " + + ex.getMessage() + + " on thread " + + Thread.currentThread().getName() + + " at " + + LocalDateTime.now().format(formatter)); + } + if (result != null) { + System.out.println(result + " on thread " + Thread.currentThread().getName()); + } + }); + } +} \ No newline at end of file diff --git a/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/TimeLimitingService.java b/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/TimeLimitingService.java new file mode 100644 index 0000000..79c681a --- /dev/null +++ b/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/TimeLimitingService.java @@ -0,0 +1,167 @@ +package io.reflectoring.resilience4j.springboot; + +import io.github.resilience4j.micrometer.tagged.TaggedTimeLimiterMetrics; +import io.github.resilience4j.ratelimiter.RequestNotPermitted; +import io.github.resilience4j.ratelimiter.annotation.RateLimiter; +import io.github.resilience4j.timelimiter.TimeLimiter.EventPublisher; +import io.github.resilience4j.timelimiter.TimeLimiterConfig; +import io.github.resilience4j.timelimiter.TimeLimiterRegistry; +import io.github.resilience4j.timelimiter.annotation.TimeLimiter; +import io.micrometer.core.instrument.Measurement; +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.reflectoring.resilience4j.springboot.model.Flight; +import io.reflectoring.resilience4j.springboot.model.SearchRequest; +import io.reflectoring.resilience4j.springboot.services.FlightSearchService; +import java.sql.Time; +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.StreamSupport; +import javax.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class TimeLimitingService { + @Autowired + private FlightSearchService remoteSearchService; + + @Autowired + private TimeLimiterRegistry timeLimiterRegistry; + +/* + void printDefaultValues() { + TimeLimiterConfig config = TimeLimiterConfig.ofDefaults(); + + System.out.println( + "getTimeoutDuration in ms = " + Duration.from(config.getTimeoutDuration()).toMillis()); + System.out.println("shouldCancelRunningFuture = " + config.shouldCancelRunningFuture()); + } */ + + + @TimeLimiter(name = "basicExample") + CompletableFuture> basicExample(SearchRequest request) { + return CompletableFuture.supplyAsync(() -> remoteSearchService.searchFlightsTakingOneSecond(request)); + } + + @TimeLimiter(name = "timeoutExample") + CompletableFuture> timeoutExample(SearchRequest request) { + return CompletableFuture.supplyAsync(() -> remoteSearchService.searchFlightsTakingOneSecond(request)); + } + + @TimeLimiter(name = "timeAndRateLimiter") + @RateLimiter(name = "timeAndRateLimiter") + CompletableFuture> aspectOrderExample(SearchRequest request) { + return CompletableFuture.supplyAsync(() -> remoteSearchService.searchFlightsTakingOneSecond(request)); + } + + /* + void basicExample_ExcecuteCompletionStage() { + TimeLimiterConfig config = TimeLimiterConfig.custom() + .timeoutDuration(Duration.ofMillis(500)) + .build(); + + TimeLimiterRegistry registry = TimeLimiterRegistry.of(config); + TimeLimiter limiter = registry.timeLimiter("flightSearch"); + + FlightSearchService service = new FlightSearchService(); + SearchRequest request = new SearchRequest("NYC", "LAX", "08/30/2020"); + + Supplier> flightSupplier = () -> service.searchFlightsTakingOneSecond(request); + Supplier>> origCompletionStageSupplier = () -> CompletableFuture + .supplyAsync(flightSupplier); + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + CompletionStage> decoratedCompletionStage = limiter + .executeCompletionStage(scheduler, origCompletionStageSupplier); + + decoratedCompletionStage.whenComplete((result, ex) -> { + if (ex != null) { + System.out.println("Exception " + + ex.getMessage() + + " on thread " + + Thread.currentThread().getName() + + " at " + + LocalDateTime.now().format(formatter)); + } + if (result != null) { + System.out.println(result + " on thread " + Thread.currentThread().getName()); + } + }); + + scheduler.shutdown(); + } + + + void whenToUseExample() { + CompletableFuture.supplyAsync(this::slowMethod).thenAccept(System.out::println); + } + + void whenToUseExample_Blocking() + throws InterruptedException, ExecutionException, TimeoutException { + CompletableFuture completableFuture = CompletableFuture + .supplyAsync(this::slowMethod); + Integer result = completableFuture.get(3000, TimeUnit.MILLISECONDS); + System.out.println(result); + } + + int slowMethod() { + System.out.println(Thread.currentThread().getName()); + // sleep to simulate delay + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return 0; + } + + static void delay(int seconds) { + // sleep to simulate delay + try { + Thread.sleep(seconds * 1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } */ + + @TimeLimiter(name = "eventsExample") + CompletableFuture> eventsExample(SearchRequest request) { + return CompletableFuture.supplyAsync(() -> remoteSearchService.searchFlightsTakingRandomTime(request)); + } + + @TimeLimiter(name = "fallbackExample", fallbackMethod = "localCacheFlightSearch") + CompletableFuture> fallbackExample(SearchRequest request) { + return CompletableFuture.supplyAsync(() -> remoteSearchService.searchFlightsTakingOneSecond(request)); + } + + private CompletableFuture> localCacheFlightSearch(SearchRequest request, TimeoutException rnp) { + System.out.println("Returning search results from cache"); + System.out.println(rnp.getMessage()); + CompletableFuture> result = new CompletableFuture<>(); + result.complete(Arrays.asList( + new Flight("XY 765", request.getFlightDate(), request.getFrom(), request.getTo()), + new Flight("XY 781", request.getFlightDate(), request.getFrom(), request.getTo()))); + return result; + } + + @PostConstruct + void postConstruct() { + EventPublisher eventPublisher = timeLimiterRegistry.timeLimiter("eventsExample").getEventPublisher(); + eventPublisher.onSuccess(System.out::println); + eventPublisher.onError(System.out::println); + eventPublisher.onTimeout(System.out::println); + } +} \ No newline at end of file diff --git a/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/services/FlightSearchService.java b/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/services/FlightSearchService.java index 973ecc6..69d380d 100644 --- a/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/services/FlightSearchService.java +++ b/resilience4j/springboot-resilience4j/src/main/java/io/reflectoring/resilience4j/springboot/services/FlightSearchService.java @@ -13,6 +13,7 @@ import java.time.format.DateTimeFormatter; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Random; import org.springframework.stereotype.Service; @Service @@ -20,6 +21,7 @@ public class FlightSearchService { PotentialFailure potentialFailure = new NoFailure(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss SSS"); + Random random = new Random(); PotentialFailureCheckedException potentialFailureCheckedException = new NoCheckedExceptionFailure(); @@ -108,4 +110,42 @@ public class FlightSearchService { response.setFlights(flights); return response; } + + public List searchFlightsTakingOneSecond(SearchRequest request) { + System.out.println("Searching for flights; " + + "current time = " + LocalDateTime.now().format(formatter) + + "; current thread = " + Thread.currentThread().getName()); + + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + List flights = Arrays.asList( + new Flight("XY 765", request.getFlightDate(), request.getFrom(), request.getTo()), + new Flight("XY 746", request.getFlightDate(), request.getFrom(), request.getTo()) + ); + System.out.println("Flight search successful at " + LocalDateTime.now().format(formatter)); + return flights; + } + + public List searchFlightsTakingRandomTime(SearchRequest request) { + long delay = random.nextInt(3000); + try { + Thread.sleep(delay); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println("Searching for flights; " + + "current time = " + LocalDateTime.now().format(formatter) + + "; current thread = " + Thread.currentThread().getName()); + + List flights = Arrays.asList( + new Flight("XY 765", request.getFlightDate(), request.getFrom(), request.getTo()), + new Flight("XY 746", request.getFlightDate(), request.getFrom(), request.getTo()) + ); + System.out.println("Flight search successful"); + return flights; + } } \ No newline at end of file diff --git a/resilience4j/springboot-resilience4j/src/main/resources/application.yml b/resilience4j/springboot-resilience4j/src/main/resources/application.yml index 729a76f..9fb055a 100644 --- a/resilience4j/springboot-resilience4j/src/main/resources/application.yml +++ b/resilience4j/springboot-resilience4j/src/main/resources/application.yml @@ -99,6 +99,25 @@ resilience4j: limitRefreshPeriod: 1s timeoutDuration: 500ms + timelimiter: + instances: + + # TimeLimiter object used in TimeLimitingService.basicExample() + basicExample: + timeoutDuration: 2s + + # TimeLimiter object used in TimeLimitingService.timeoutExample() + timeoutExample: + timeoutDuration: 500ms + + # TimeLimiter object used in TimeLimitingService.eventsExample() + eventsExample: + timeoutDuration: 2s + + # TimeLimiter object used in TimeLimitingService.fallbackExample() + fallbackExample: + timeoutDuration: 500ms + management: endpoints: web: