BAEL-6071: Resilience4j events endpoints (#13697)
* Add project for Resilience4j Events Endpoints article * Update README * Add spring-boot-resilience4j module * Renamed test class to meet constraints * Update package structure * Added formatting * Replaces .parallel() with Executor * Updated concurrent calls for bulkhead test
This commit is contained in:
parent
9df061c513
commit
745e372844
@ -93,6 +93,7 @@
|
|||||||
<module>spring-boot-3-native</module>
|
<module>spring-boot-3-native</module>
|
||||||
<module>spring-boot-3-observation</module>
|
<module>spring-boot-3-observation</module>
|
||||||
<module>spring-boot-3-test-pitfalls</module>
|
<module>spring-boot-3-test-pitfalls</module>
|
||||||
|
<module>spring-boot-resilience4j</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
|
3
spring-boot-modules/spring-boot-resilience4j/README.md
Normal file
3
spring-boot-modules/spring-boot-resilience4j/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
### Relevant Articles:
|
||||||
|
|
||||||
|
- [Resilience4j Events Endpoints](https://www.baeldung.com/resilience4j-events-endpoints)
|
53
spring-boot-modules/spring-boot-resilience4j/pom.xml
Normal file
53
spring-boot-modules/spring-boot-resilience4j/pom.xml
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>com.example</groupId>
|
||||||
|
<artifactId>spring-boot-resilience4j</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<name>spring-boot-resilience4j</name>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.baeldung.spring-boot-modules</groupId>
|
||||||
|
<artifactId>spring-boot-modules</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-aop</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.github.resilience4j</groupId>
|
||||||
|
<artifactId>resilience4j-spring-boot2</artifactId>
|
||||||
|
<version>2.0.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||||
|
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||||
|
<version>2.14.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.tomakehurst</groupId>
|
||||||
|
<artifactId>wiremock-jre8</artifactId>
|
||||||
|
<version>2.35.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
@ -0,0 +1,29 @@
|
|||||||
|
package com.baeldung.resilience4j.eventendpoints;
|
||||||
|
|
||||||
|
import io.github.resilience4j.bulkhead.BulkheadFullException;
|
||||||
|
import io.github.resilience4j.circuitbreaker.CallNotPermittedException;
|
||||||
|
import io.github.resilience4j.ratelimiter.RequestNotPermitted;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
|
||||||
|
@ControllerAdvice
|
||||||
|
public class ApiExceptionHandler {
|
||||||
|
@ExceptionHandler({CallNotPermittedException.class})
|
||||||
|
@ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
|
||||||
|
public void handleCallNotPermittedException() {}
|
||||||
|
|
||||||
|
@ExceptionHandler({TimeoutException.class})
|
||||||
|
@ResponseStatus(HttpStatus.REQUEST_TIMEOUT)
|
||||||
|
public void handleTimeoutException() {}
|
||||||
|
|
||||||
|
@ExceptionHandler({BulkheadFullException.class})
|
||||||
|
@ResponseStatus(HttpStatus.BANDWIDTH_LIMIT_EXCEEDED)
|
||||||
|
public void handleBulkheadFullException() {}
|
||||||
|
|
||||||
|
@ExceptionHandler({RequestNotPermitted.class})
|
||||||
|
@ResponseStatus(HttpStatus.TOO_MANY_REQUESTS)
|
||||||
|
public void handleRequestNotPermitted() {}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package com.baeldung.resilience4j.eventendpoints;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class ExternalAPICaller {
|
||||||
|
private final RestTemplate restTemplate;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public ExternalAPICaller(RestTemplate restTemplate) {
|
||||||
|
this.restTemplate = restTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String callApi() {
|
||||||
|
return restTemplate.getForObject("/api/external", String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String callApiWithDelay() {
|
||||||
|
String result = restTemplate.getForObject("/api/external", String.class);
|
||||||
|
try {
|
||||||
|
Thread.sleep(5000);
|
||||||
|
} catch (InterruptedException ignore) {
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package com.baeldung.resilience4j.eventendpoints;
|
||||||
|
|
||||||
|
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class ExternalApiCallerConfig {
|
||||||
|
@Bean
|
||||||
|
public RestTemplate restTemplate() {
|
||||||
|
return new RestTemplateBuilder().rootUri("http://localhost:9090").build();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package com.baeldung.resilience4j.eventendpoints;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication()
|
||||||
|
public class ResilientApp {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(ResilientApp.class, args);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
package com.baeldung.resilience4j.eventendpoints;
|
||||||
|
|
||||||
|
import io.github.resilience4j.bulkhead.annotation.Bulkhead;
|
||||||
|
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
|
||||||
|
import io.github.resilience4j.ratelimiter.annotation.RateLimiter;
|
||||||
|
import io.github.resilience4j.retry.annotation.Retry;
|
||||||
|
import io.github.resilience4j.timelimiter.annotation.TimeLimiter;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/")
|
||||||
|
public class ResilientAppController {
|
||||||
|
|
||||||
|
private final ExternalAPICaller externalAPICaller;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public ResilientAppController(ExternalAPICaller externalApi) {
|
||||||
|
this.externalAPICaller = externalApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/circuit-breaker")
|
||||||
|
@CircuitBreaker(name = "externalService")
|
||||||
|
public String circuitBreakerApi() {
|
||||||
|
return externalAPICaller.callApi();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/retry")
|
||||||
|
@Retry(name = "externalService", fallbackMethod = "fallbackAfterRetry")
|
||||||
|
public String retryApi() {
|
||||||
|
return externalAPICaller.callApi();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/bulkhead")
|
||||||
|
@Bulkhead(name = "externalService")
|
||||||
|
public String bulkheadApi() {
|
||||||
|
return externalAPICaller.callApi();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/rate-limiter")
|
||||||
|
@RateLimiter(name = "externalService")
|
||||||
|
public String rateLimitApi() {
|
||||||
|
return externalAPICaller.callApi();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/time-limiter")
|
||||||
|
@TimeLimiter(name = "externalService")
|
||||||
|
public CompletableFuture<String> timeLimiterApi() {
|
||||||
|
return CompletableFuture.supplyAsync(externalAPICaller::callApiWithDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String fallbackAfterRetry(Exception ex) {
|
||||||
|
return "all retries have exhausted";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
management:
|
||||||
|
endpoints:
|
||||||
|
web:
|
||||||
|
exposure:
|
||||||
|
include: '*'
|
||||||
|
|
||||||
|
resilience4j.circuitbreaker:
|
||||||
|
configs:
|
||||||
|
default:
|
||||||
|
registerHealthIndicator: true
|
||||||
|
slidingWindowSize: 10
|
||||||
|
minimumNumberOfCalls: 5
|
||||||
|
permittedNumberOfCallsInHalfOpenState: 3
|
||||||
|
automaticTransitionFromOpenToHalfOpenEnabled: true
|
||||||
|
waitDurationInOpenState: 5s
|
||||||
|
failureRateThreshold: 50
|
||||||
|
eventConsumerBufferSize: 50
|
||||||
|
instances:
|
||||||
|
externalService:
|
||||||
|
baseConfig: default
|
||||||
|
|
||||||
|
resilience4j.retry:
|
||||||
|
configs:
|
||||||
|
default:
|
||||||
|
maxAttempts: 3
|
||||||
|
waitDuration: 100
|
||||||
|
instances:
|
||||||
|
externalService:
|
||||||
|
baseConfig: default
|
||||||
|
|
||||||
|
resilience4j.timelimiter:
|
||||||
|
configs:
|
||||||
|
default:
|
||||||
|
cancelRunningFuture: true
|
||||||
|
timeoutDuration: 2s
|
||||||
|
instances:
|
||||||
|
externalService:
|
||||||
|
baseConfig: default
|
||||||
|
|
||||||
|
resilience4j.bulkhead:
|
||||||
|
configs:
|
||||||
|
default:
|
||||||
|
max-concurrent-calls: 3
|
||||||
|
max-wait-duration: 1
|
||||||
|
instances:
|
||||||
|
externalService:
|
||||||
|
baseConfig: default
|
||||||
|
|
||||||
|
resilience4j.ratelimiter:
|
||||||
|
configs:
|
||||||
|
default:
|
||||||
|
limit-for-period: 5
|
||||||
|
limit-refresh-period: 60s
|
||||||
|
timeout-duration: 0s
|
||||||
|
allow-health-indicator-to-fail: true
|
||||||
|
subscribe-for-events: true
|
||||||
|
event-consumer-buffer-size: 50
|
||||||
|
instances:
|
||||||
|
externalService:
|
||||||
|
baseConfig: default
|
@ -0,0 +1,318 @@
|
|||||||
|
package com.baeldung.resilience4j.eventendpoints;
|
||||||
|
|
||||||
|
import static com.github.tomakehurst.wiremock.client.WireMock.*;
|
||||||
|
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.springframework.http.HttpStatus.*;
|
||||||
|
|
||||||
|
import com.baeldung.resilience4j.eventendpoints.model.*;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.JSR310Module;
|
||||||
|
import com.github.tomakehurst.wiremock.client.WireMock;
|
||||||
|
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
|
||||||
|
import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||||
|
import org.springframework.boot.test.web.server.LocalServerPort;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
|
||||||
|
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||||
|
class ResilientAppControllerIntegrationTest {
|
||||||
|
|
||||||
|
@Autowired private TestRestTemplate restTemplate;
|
||||||
|
|
||||||
|
@LocalServerPort private Integer port;
|
||||||
|
|
||||||
|
private static final ObjectMapper objectMapper =
|
||||||
|
new ObjectMapper().registerModule(new JSR310Module());
|
||||||
|
|
||||||
|
@RegisterExtension
|
||||||
|
static WireMockExtension EXTERNAL_SERVICE =
|
||||||
|
WireMockExtension.newInstance()
|
||||||
|
.options(WireMockConfiguration.wireMockConfig().port(9090))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCircuitBreakerEvents() throws Exception {
|
||||||
|
EXTERNAL_SERVICE.stubFor(WireMock.get("/api/external").willReturn(serverError()));
|
||||||
|
|
||||||
|
IntStream.rangeClosed(1, 5)
|
||||||
|
.forEach(
|
||||||
|
i -> {
|
||||||
|
ResponseEntity<String> response =
|
||||||
|
restTemplate.getForEntity("/api/circuit-breaker", String.class);
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch the events generated by the above calls
|
||||||
|
List<CircuitBreakerEvent> circuitBreakerEvents = getCircuitBreakerEvents();
|
||||||
|
assertThat(circuitBreakerEvents.size()).isEqualTo(7);
|
||||||
|
|
||||||
|
// The first 5 events are the error events corresponding to the above server error responses
|
||||||
|
IntStream.rangeClosed(0, 4)
|
||||||
|
.forEach(
|
||||||
|
i -> {
|
||||||
|
assertThat(circuitBreakerEvents.get(i).getCircuitBreakerName())
|
||||||
|
.isEqualTo("externalService");
|
||||||
|
assertThat(circuitBreakerEvents.get(i).getType()).isEqualTo("ERROR");
|
||||||
|
assertThat(circuitBreakerEvents.get(i).getCreationTime()).isNotNull();
|
||||||
|
assertThat(circuitBreakerEvents.get(i).getErrorMessage()).isNotNull();
|
||||||
|
assertThat(circuitBreakerEvents.get(i).getDurationInMs()).isNotNull();
|
||||||
|
assertThat(circuitBreakerEvents.get(i).getStateTransition()).isNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Following event signals the configured failure rate exceeded
|
||||||
|
CircuitBreakerEvent failureRateExceededEvent = circuitBreakerEvents.get(5);
|
||||||
|
assertThat(failureRateExceededEvent.getCircuitBreakerName()).isEqualTo("externalService");
|
||||||
|
assertThat(failureRateExceededEvent.getType()).isEqualTo("FAILURE_RATE_EXCEEDED");
|
||||||
|
assertThat(failureRateExceededEvent.getCreationTime()).isNotNull();
|
||||||
|
assertThat(failureRateExceededEvent.getErrorMessage()).isNull();
|
||||||
|
assertThat(failureRateExceededEvent.getDurationInMs()).isNull();
|
||||||
|
assertThat(failureRateExceededEvent.getStateTransition()).isNull();
|
||||||
|
|
||||||
|
// Following event signals the state transition from CLOSED TO OPEN
|
||||||
|
CircuitBreakerEvent stateTransitionEvent = circuitBreakerEvents.get(6);
|
||||||
|
assertThat(stateTransitionEvent.getCircuitBreakerName()).isEqualTo("externalService");
|
||||||
|
assertThat(stateTransitionEvent.getType()).isEqualTo("STATE_TRANSITION");
|
||||||
|
assertThat(stateTransitionEvent.getCreationTime()).isNotNull();
|
||||||
|
assertThat(stateTransitionEvent.getErrorMessage()).isNull();
|
||||||
|
assertThat(stateTransitionEvent.getDurationInMs()).isNull();
|
||||||
|
assertThat(stateTransitionEvent.getStateTransition()).isEqualTo("CLOSED_TO_OPEN");
|
||||||
|
|
||||||
|
IntStream.rangeClosed(1, 5)
|
||||||
|
.forEach(
|
||||||
|
i -> {
|
||||||
|
ResponseEntity<String> response =
|
||||||
|
restTemplate.getForEntity("/api/circuit-breaker", String.class);
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE);
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Fetch the events generated by the above calls
|
||||||
|
List<CircuitBreakerEvent> updatedCircuitBreakerEvents = getCircuitBreakerEvents();
|
||||||
|
assertThat(updatedCircuitBreakerEvents.size()).isEqualTo(12);
|
||||||
|
|
||||||
|
// Newly added events will be of type NOT_PERMITTED since the Circuit Breaker is in OPEN state
|
||||||
|
IntStream.rangeClosed(7, 11)
|
||||||
|
.forEach(
|
||||||
|
i -> {
|
||||||
|
assertThat(updatedCircuitBreakerEvents.get(i).getCircuitBreakerName())
|
||||||
|
.isEqualTo("externalService");
|
||||||
|
assertThat(updatedCircuitBreakerEvents.get(i).getType()).isEqualTo("NOT_PERMITTED");
|
||||||
|
assertThat(updatedCircuitBreakerEvents.get(i).getCreationTime()).isNotNull();
|
||||||
|
assertThat(updatedCircuitBreakerEvents.get(i).getErrorMessage()).isNull();
|
||||||
|
assertThat(updatedCircuitBreakerEvents.get(i).getDurationInMs()).isNull();
|
||||||
|
assertThat(updatedCircuitBreakerEvents.get(i).getStateTransition()).isNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
EXTERNAL_SERVICE.verify(5, getRequestedFor(urlEqualTo("/api/external")));
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<CircuitBreakerEvent> getCircuitBreakerEvents() throws Exception {
|
||||||
|
String jsonEventsList =
|
||||||
|
IOUtils.toString(
|
||||||
|
new URI("http://localhost:" + port + "/actuator/circuitbreakerevents"),
|
||||||
|
Charset.forName("UTF-8"));
|
||||||
|
CircuitBreakerEvents circuitBreakerEvents =
|
||||||
|
objectMapper.readValue(jsonEventsList, CircuitBreakerEvents.class);
|
||||||
|
return circuitBreakerEvents.getCircuitBreakerEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRetryEvents() throws Exception {
|
||||||
|
EXTERNAL_SERVICE.stubFor(WireMock.get("/api/external").willReturn(ok()));
|
||||||
|
ResponseEntity<String> response1 = restTemplate.getForEntity("/api/retry", String.class);
|
||||||
|
EXTERNAL_SERVICE.verify(1, getRequestedFor(urlEqualTo("/api/external")));
|
||||||
|
|
||||||
|
EXTERNAL_SERVICE.resetRequests();
|
||||||
|
|
||||||
|
EXTERNAL_SERVICE.stubFor(WireMock.get("/api/external").willReturn(serverError()));
|
||||||
|
ResponseEntity<String> response2 = restTemplate.getForEntity("/api/retry", String.class);
|
||||||
|
assertThat(response2.getBody()).isEqualTo("all retries have exhausted");
|
||||||
|
EXTERNAL_SERVICE.verify(3, getRequestedFor(urlEqualTo("/api/external")));
|
||||||
|
|
||||||
|
List<RetryEvent> retryEvents = getRetryEvents();
|
||||||
|
assertThat(retryEvents.size()).isEqualTo(3);
|
||||||
|
|
||||||
|
// First 2 events should be retry events
|
||||||
|
IntStream.rangeClosed(0, 1)
|
||||||
|
.forEach(
|
||||||
|
i -> {
|
||||||
|
assertThat(retryEvents.get(i).getRetryName()).isEqualTo("externalService");
|
||||||
|
assertThat(retryEvents.get(i).getType()).isEqualTo("RETRY");
|
||||||
|
assertThat(retryEvents.get(i).getCreationTime()).isNotNull();
|
||||||
|
assertThat(retryEvents.get(i).getErrorMessage()).isNotNull();
|
||||||
|
assertThat(retryEvents.get(i).getNumberOfAttempts()).isEqualTo(i + 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Last event should be an error event because the configured num of retries is reached
|
||||||
|
RetryEvent errorRetryEvent = retryEvents.get(2);
|
||||||
|
assertThat(errorRetryEvent.getRetryName()).isEqualTo("externalService");
|
||||||
|
assertThat(errorRetryEvent.getType()).isEqualTo("ERROR");
|
||||||
|
assertThat(errorRetryEvent.getCreationTime()).isNotNull();
|
||||||
|
assertThat(errorRetryEvent.getErrorMessage()).isNotNull();
|
||||||
|
assertThat(errorRetryEvent.getNumberOfAttempts()).isEqualTo(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<RetryEvent> getRetryEvents() throws Exception {
|
||||||
|
String jsonEventsList =
|
||||||
|
IOUtils.toString(
|
||||||
|
new URI("http://localhost:" + port + "/actuator/retryevents"),
|
||||||
|
Charset.forName("UTF-8"));
|
||||||
|
RetryEvents retryEvents = objectMapper.readValue(jsonEventsList, RetryEvents.class);
|
||||||
|
return retryEvents.getRetryEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testTimeLimiterEvents() throws Exception {
|
||||||
|
EXTERNAL_SERVICE.stubFor(WireMock.get("/api/external").willReturn(ok()));
|
||||||
|
ResponseEntity<String> response = restTemplate.getForEntity("/api/time-limiter", String.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.REQUEST_TIMEOUT);
|
||||||
|
EXTERNAL_SERVICE.verify(1, getRequestedFor(urlEqualTo("/api/external")));
|
||||||
|
|
||||||
|
List<TimeLimiterEvent> timeLimiterEvents = getTimeLimiterEvents();
|
||||||
|
assertThat(timeLimiterEvents.size()).isEqualTo(1);
|
||||||
|
TimeLimiterEvent timeoutEvent = timeLimiterEvents.get(0);
|
||||||
|
assertThat(timeoutEvent.getTimeLimiterName()).isEqualTo("externalService");
|
||||||
|
assertThat(timeoutEvent.getType()).isEqualTo("TIMEOUT");
|
||||||
|
assertThat(timeoutEvent.getCreationTime()).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TimeLimiterEvent> getTimeLimiterEvents() throws Exception {
|
||||||
|
String jsonEventsList =
|
||||||
|
IOUtils.toString(
|
||||||
|
new URI("http://localhost:" + port + "/actuator/timelimiterevents"),
|
||||||
|
Charset.forName("UTF-8"));
|
||||||
|
TimeLimiterEvents timeLimiterEvents =
|
||||||
|
objectMapper.readValue(jsonEventsList, TimeLimiterEvents.class);
|
||||||
|
return timeLimiterEvents.getTimeLimiterEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testBulkheadEvents() throws Exception {
|
||||||
|
EXTERNAL_SERVICE.stubFor(WireMock.get("/api/external").willReturn(ok()));
|
||||||
|
Map<Integer, Integer> responseStatusCount = new ConcurrentHashMap<>();
|
||||||
|
ExecutorService executorService = Executors.newFixedThreadPool(5);
|
||||||
|
|
||||||
|
List<Callable<Integer>> tasks = new ArrayList<>();
|
||||||
|
IntStream.rangeClosed(1, 5)
|
||||||
|
.forEach(
|
||||||
|
i ->
|
||||||
|
tasks.add(
|
||||||
|
() -> {
|
||||||
|
ResponseEntity<String> response =
|
||||||
|
restTemplate.getForEntity("/api/bulkhead", String.class);
|
||||||
|
return response.getStatusCodeValue();
|
||||||
|
}));
|
||||||
|
|
||||||
|
List<Future<Integer>> futures = executorService.invokeAll(tasks);
|
||||||
|
for (Future<Integer> future : futures) {
|
||||||
|
int statusCode = future.get();
|
||||||
|
responseStatusCount.merge(statusCode, 1, Integer::sum);
|
||||||
|
}
|
||||||
|
executorService.shutdown();
|
||||||
|
|
||||||
|
assertEquals(2, responseStatusCount.keySet().size());
|
||||||
|
assertTrue(responseStatusCount.containsKey(BANDWIDTH_LIMIT_EXCEEDED.value()));
|
||||||
|
assertTrue(responseStatusCount.containsKey(OK.value()));
|
||||||
|
EXTERNAL_SERVICE.verify(3, getRequestedFor(urlEqualTo("/api/external")));
|
||||||
|
|
||||||
|
List<BulkheadEvent> bulkheadEvents = getBulkheadEvents();
|
||||||
|
|
||||||
|
// Based on the configuration, the first 3 calls should be permitted, so we should see the
|
||||||
|
// CALL_PERMITTED events
|
||||||
|
IntStream.rangeClosed(0, 2)
|
||||||
|
.forEach(
|
||||||
|
i -> {
|
||||||
|
assertThat(bulkheadEvents.get(i).getBulkheadName()).isEqualTo("externalService");
|
||||||
|
assertThat(bulkheadEvents.get(i).getType()).isEqualTo("CALL_PERMITTED");
|
||||||
|
assertThat(bulkheadEvents.get(i).getCreationTime()).isNotNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
// For the other 2 calls made we should see the CALL_REJECTED events
|
||||||
|
IntStream.rangeClosed(3, 4)
|
||||||
|
.forEach(
|
||||||
|
i -> {
|
||||||
|
assertThat(bulkheadEvents.get(i).getBulkheadName()).isEqualTo("externalService");
|
||||||
|
assertThat(bulkheadEvents.get(i).getType()).isEqualTo("CALL_REJECTED");
|
||||||
|
assertThat(bulkheadEvents.get(i).getCreationTime()).isNotNull();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<BulkheadEvent> getBulkheadEvents() throws Exception {
|
||||||
|
String jsonEventsList =
|
||||||
|
IOUtils.toString(
|
||||||
|
new URI("http://localhost:" + port + "/actuator/bulkheadevents"),
|
||||||
|
Charset.forName("UTF-8"));
|
||||||
|
BulkheadEvents bulkheadEvents = objectMapper.readValue(jsonEventsList, BulkheadEvents.class);
|
||||||
|
return bulkheadEvents.getBulkheadEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRateLimiterEvents() throws Exception {
|
||||||
|
EXTERNAL_SERVICE.stubFor(WireMock.get("/api/external").willReturn(ok()));
|
||||||
|
Map<Integer, Integer> responseStatusCount = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
IntStream.rangeClosed(1, 50)
|
||||||
|
.forEach(
|
||||||
|
i -> {
|
||||||
|
ResponseEntity<String> response =
|
||||||
|
restTemplate.getForEntity("/api/rate-limiter", String.class);
|
||||||
|
int statusCode = response.getStatusCodeValue();
|
||||||
|
responseStatusCount.put(
|
||||||
|
statusCode, responseStatusCount.getOrDefault(statusCode, 0) + 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
assertEquals(2, responseStatusCount.keySet().size());
|
||||||
|
assertTrue(responseStatusCount.containsKey(TOO_MANY_REQUESTS.value()));
|
||||||
|
assertTrue(responseStatusCount.containsKey(OK.value()));
|
||||||
|
EXTERNAL_SERVICE.verify(5, getRequestedFor(urlEqualTo("/api/external")));
|
||||||
|
|
||||||
|
List<RateLimiterEvent> rateLimiterEvents = getRateLimiterEvents();
|
||||||
|
assertThat(rateLimiterEvents.size()).isEqualTo(50);
|
||||||
|
|
||||||
|
// First allowed calls in the rate limit is 5, so we should see for those SUCCESSFUL_ACQUIRE
|
||||||
|
// events
|
||||||
|
IntStream.rangeClosed(0, 4)
|
||||||
|
.forEach(
|
||||||
|
i -> {
|
||||||
|
assertThat(rateLimiterEvents.get(i).getRateLimiterName())
|
||||||
|
.isEqualTo("externalService");
|
||||||
|
assertThat(rateLimiterEvents.get(i).getType()).isEqualTo("SUCCESSFUL_ACQUIRE");
|
||||||
|
assertThat(rateLimiterEvents.get(i).getCreationTime()).isNotNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
// the rest should be FAILED_ACQUIRE events since the rate limiter kicks in
|
||||||
|
IntStream.rangeClosed(5, rateLimiterEvents.size() - 1)
|
||||||
|
.forEach(
|
||||||
|
i -> {
|
||||||
|
assertThat(rateLimiterEvents.get(i).getRateLimiterName())
|
||||||
|
.isEqualTo("externalService");
|
||||||
|
assertThat(rateLimiterEvents.get(i).getType()).isEqualTo("FAILED_ACQUIRE");
|
||||||
|
assertThat(rateLimiterEvents.get(i).getCreationTime()).isNotNull();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<RateLimiterEvent> getRateLimiterEvents() throws Exception {
|
||||||
|
String jsonEventsList =
|
||||||
|
IOUtils.toString(
|
||||||
|
new URI("http://localhost:" + port + "/actuator/ratelimiterevents"),
|
||||||
|
Charset.forName("UTF-8"));
|
||||||
|
RateLimiterEvents rateLimiterEvents =
|
||||||
|
objectMapper.readValue(jsonEventsList, RateLimiterEvents.class);
|
||||||
|
return rateLimiterEvents.getRateLimiterEvents();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package com.baeldung.resilience4j.eventendpoints.model;
|
||||||
|
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class BulkheadEvent {
|
||||||
|
|
||||||
|
private String bulkheadName;
|
||||||
|
private String type;
|
||||||
|
private ZonedDateTime creationTime;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
BulkheadEvent that = (BulkheadEvent) o;
|
||||||
|
return Objects.equals(bulkheadName, that.bulkheadName)
|
||||||
|
&& Objects.equals(type, that.type)
|
||||||
|
&& Objects.equals(creationTime, that.creationTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(bulkheadName, type, creationTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBulkheadName() {
|
||||||
|
return bulkheadName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBulkheadName(String bulkheadName) {
|
||||||
|
this.bulkheadName = bulkheadName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZonedDateTime getCreationTime() {
|
||||||
|
return creationTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreationTime(ZonedDateTime creationTime) {
|
||||||
|
this.creationTime = creationTime;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.baeldung.resilience4j.eventendpoints.model;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class BulkheadEvents {
|
||||||
|
|
||||||
|
private List<BulkheadEvent> bulkheadEvents;
|
||||||
|
|
||||||
|
public List<BulkheadEvent> getBulkheadEvents() {
|
||||||
|
return bulkheadEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBulkheadEvents(List<BulkheadEvent> bulkheadEvents) {
|
||||||
|
this.bulkheadEvents = bulkheadEvents;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
package com.baeldung.resilience4j.eventendpoints.model;
|
||||||
|
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class CircuitBreakerEvent {
|
||||||
|
|
||||||
|
private String circuitBreakerName;
|
||||||
|
private String type;
|
||||||
|
private ZonedDateTime creationTime;
|
||||||
|
private String errorMessage;
|
||||||
|
private Integer durationInMs;
|
||||||
|
private String stateTransition;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
CircuitBreakerEvent that = (CircuitBreakerEvent) o;
|
||||||
|
return Objects.equals(circuitBreakerName, that.circuitBreakerName)
|
||||||
|
&& Objects.equals(type, that.type)
|
||||||
|
&& Objects.equals(creationTime, that.creationTime)
|
||||||
|
&& Objects.equals(errorMessage, that.errorMessage)
|
||||||
|
&& Objects.equals(durationInMs, that.durationInMs)
|
||||||
|
&& Objects.equals(stateTransition, that.stateTransition);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(
|
||||||
|
circuitBreakerName, type, creationTime, errorMessage, durationInMs, stateTransition);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCircuitBreakerName() {
|
||||||
|
return circuitBreakerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCircuitBreakerName(String circuitBreakerName) {
|
||||||
|
this.circuitBreakerName = circuitBreakerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZonedDateTime getCreationTime() {
|
||||||
|
return creationTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreationTime(ZonedDateTime creationTime) {
|
||||||
|
this.creationTime = creationTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getErrorMessage() {
|
||||||
|
return errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setErrorMessage(String errorMessage) {
|
||||||
|
this.errorMessage = errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getDurationInMs() {
|
||||||
|
return durationInMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDurationInMs(Integer durationInMs) {
|
||||||
|
this.durationInMs = durationInMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStateTransition() {
|
||||||
|
return stateTransition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStateTransition(String stateTransition) {
|
||||||
|
this.stateTransition = stateTransition;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.baeldung.resilience4j.eventendpoints.model;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class CircuitBreakerEvents {
|
||||||
|
|
||||||
|
private List<CircuitBreakerEvent> circuitBreakerEvents;
|
||||||
|
|
||||||
|
public List<CircuitBreakerEvent> getCircuitBreakerEvents() {
|
||||||
|
return circuitBreakerEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCircuitBreakerEvents(List<CircuitBreakerEvent> circuitBreakerEvents) {
|
||||||
|
this.circuitBreakerEvents = circuitBreakerEvents;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package com.baeldung.resilience4j.eventendpoints.model;
|
||||||
|
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class RateLimiterEvent {
|
||||||
|
|
||||||
|
private String rateLimiterName;
|
||||||
|
private String type;
|
||||||
|
private ZonedDateTime creationTime;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
RateLimiterEvent that = (RateLimiterEvent) o;
|
||||||
|
return Objects.equals(rateLimiterName, that.rateLimiterName)
|
||||||
|
&& Objects.equals(type, that.type)
|
||||||
|
&& Objects.equals(creationTime, that.creationTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(rateLimiterName, type, creationTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRateLimiterName() {
|
||||||
|
return rateLimiterName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRateLimiterName(String rateLimiterName) {
|
||||||
|
this.rateLimiterName = rateLimiterName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZonedDateTime getCreationTime() {
|
||||||
|
return creationTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreationTime(ZonedDateTime creationTime) {
|
||||||
|
this.creationTime = creationTime;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.baeldung.resilience4j.eventendpoints.model;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class RateLimiterEvents {
|
||||||
|
|
||||||
|
private List<RateLimiterEvent> rateLimiterEvents;
|
||||||
|
|
||||||
|
public List<RateLimiterEvent> getRateLimiterEvents() {
|
||||||
|
return rateLimiterEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRateLimiterEvents(List<RateLimiterEvent> rateLimiterEvents) {
|
||||||
|
this.rateLimiterEvents = rateLimiterEvents;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
package com.baeldung.resilience4j.eventendpoints.model;
|
||||||
|
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class RetryEvent {
|
||||||
|
|
||||||
|
private String retryName;
|
||||||
|
private String type;
|
||||||
|
private ZonedDateTime creationTime;
|
||||||
|
private String errorMessage;
|
||||||
|
private Integer numberOfAttempts;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
RetryEvent that = (RetryEvent) o;
|
||||||
|
return Objects.equals(retryName, that.retryName)
|
||||||
|
&& Objects.equals(type, that.type)
|
||||||
|
&& Objects.equals(creationTime, that.creationTime)
|
||||||
|
&& Objects.equals(errorMessage, that.errorMessage)
|
||||||
|
&& Objects.equals(numberOfAttempts, that.numberOfAttempts);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(retryName, type, creationTime, errorMessage, numberOfAttempts);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRetryName() {
|
||||||
|
return retryName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRetryName(String retryName) {
|
||||||
|
this.retryName = retryName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZonedDateTime getCreationTime() {
|
||||||
|
return creationTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreationTime(ZonedDateTime creationTime) {
|
||||||
|
this.creationTime = creationTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getErrorMessage() {
|
||||||
|
return errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setErrorMessage(String errorMessage) {
|
||||||
|
this.errorMessage = errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getNumberOfAttempts() {
|
||||||
|
return numberOfAttempts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNumberOfAttempts(Integer numberOfAttempts) {
|
||||||
|
this.numberOfAttempts = numberOfAttempts;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.baeldung.resilience4j.eventendpoints.model;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class RetryEvents {
|
||||||
|
|
||||||
|
private List<RetryEvent> retryEvents;
|
||||||
|
|
||||||
|
public List<RetryEvent> getRetryEvents() {
|
||||||
|
return retryEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRetryEvents(List<RetryEvent> retryEvents) {
|
||||||
|
this.retryEvents = retryEvents;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package com.baeldung.resilience4j.eventendpoints.model;
|
||||||
|
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class TimeLimiterEvent {
|
||||||
|
|
||||||
|
private String timeLimiterName;
|
||||||
|
private String type;
|
||||||
|
private ZonedDateTime creationTime;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
TimeLimiterEvent that = (TimeLimiterEvent) o;
|
||||||
|
return Objects.equals(timeLimiterName, that.timeLimiterName)
|
||||||
|
&& Objects.equals(type, that.type)
|
||||||
|
&& Objects.equals(creationTime, that.creationTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(timeLimiterName, type, creationTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTimeLimiterName() {
|
||||||
|
return timeLimiterName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimeLimiterName(String timeLimiterName) {
|
||||||
|
this.timeLimiterName = timeLimiterName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZonedDateTime getCreationTime() {
|
||||||
|
return creationTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreationTime(ZonedDateTime creationTime) {
|
||||||
|
this.creationTime = creationTime;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.baeldung.resilience4j.eventendpoints.model;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class TimeLimiterEvents {
|
||||||
|
|
||||||
|
private List<TimeLimiterEvent> timeLimiterEvents;
|
||||||
|
|
||||||
|
public List<TimeLimiterEvent> getTimeLimiterEvents() {
|
||||||
|
return timeLimiterEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimeLimiterEvents(List<TimeLimiterEvent> timeLimiterEvents) {
|
||||||
|
this.timeLimiterEvents = timeLimiterEvents;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user