deps: Refactor dependencies (#3224)

* remove spring dep
move junit, logging, mockito under dep mgmt

* upgrade anti-corruption-layer deps

* async method invocation

* balking, bloc

* bridge to bytecode

* caching

* callback - cqrs

* component - health check

* hexagonal - metadata mapping

* rest of the patterns

* remove checkstyle, take spotless into use
This commit is contained in:
Ilkka Seppälä authored and GitHub committed 2025-03-29 19:34:27 +02:00
1 parent 371439aeaa
commit 0ca162a55c
1863 files changed
+14403 -17632

No files matched your search

+8
View File
@@ -34,6 +34,14 @@
</parent>
<artifactId>circuit-breaker</artifactId>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
@@ -27,33 +27,29 @@ package com.iluwatar.circuitbreaker;
import lombok.extern.slf4j.Slf4j;
/**
* <p>
* The intention of the Circuit Builder pattern is to handle remote failures robustly, which is to
* mean that if a service is dependent on n number of other services, and m of them fail, we should
* be able to recover from that failure by ensuring that the user can still use the services that
* are actually functional, and resources are not tied up by uselessly by the services which are not
* working. However, we should also be able to detect when any of the m failing services become
* operational again, so that we can use it
* </p>
* <p>
* In this example, the circuit breaker pattern is demonstrated by using three services: {@link
*
* <p>In this example, the circuit breaker pattern is demonstrated by using three services: {@link
* DelayedRemoteService}, {@link QuickRemoteService} and {@link MonitoringService}. The monitoring
* service is responsible for calling three services: a local service, a quick remove service
* {@link QuickRemoteService} and a delayed remote service {@link DelayedRemoteService} , and by
* using the circuit breaker construction we ensure that if the call to remote service is going to
* fail, we are going to save our resources and not make the function call at all, by wrapping our
* call to the remote services in the {@link DefaultCircuitBreaker} implementation object.
* </p>
* <p>
* This works as follows: The {@link DefaultCircuitBreaker} object can be in one of three states:
* <b>Open</b>, <b>Closed</b> and <b>Half-Open</b>, which represents the real world circuits. If
* the state is closed (initial), we assume everything is alright and perform the function call.
* service is responsible for calling three services: a local service, a quick remove service {@link
* QuickRemoteService} and a delayed remote service {@link DelayedRemoteService} , and by using the
* circuit breaker construction we ensure that if the call to remote service is going to fail, we
* are going to save our resources and not make the function call at all, by wrapping our call to
* the remote services in the {@link DefaultCircuitBreaker} implementation object.
*
* <p>This works as follows: The {@link DefaultCircuitBreaker} object can be in one of three states:
* <b>Open</b>, <b>Closed</b> and <b>Half-Open</b>, which represents the real world circuits. If the
* state is closed (initial), we assume everything is alright and perform the function call.
* However, every time the call fails, we note it and once it crosses a threshold, we set the state
* to Open, preventing any further calls to the remote server. Then, after a certain retry period
* (during which we expect thee service to recover), we make another call to the remote server and
* this state is called the Half-Open state, where it stays till the service is down, and once it
* recovers, it goes back to the closed state and the cycle continues.
* </p>
*/
@Slf4j
public class App {
@@ -68,45 +64,45 @@ public class App {
var serverStartTime = System.nanoTime();
var delayedService = new DelayedRemoteService(serverStartTime, 5);
var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000, 2,
2000 * 1000 * 1000);
var delayedServiceCircuitBreaker =
new DefaultCircuitBreaker(delayedService, 3000, 2, 2000 * 1000 * 1000);
var quickService = new QuickRemoteService();
var quickServiceCircuitBreaker = new DefaultCircuitBreaker(quickService, 3000, 2,
2000 * 1000 * 1000);
var quickServiceCircuitBreaker =
new DefaultCircuitBreaker(quickService, 3000, 2, 2000 * 1000 * 1000);
//Create an object of monitoring service which makes both local and remote calls
var monitoringService = new MonitoringService(delayedServiceCircuitBreaker,
quickServiceCircuitBreaker);
// Create an object of monitoring service which makes both local and remote calls
var monitoringService =
new MonitoringService(delayedServiceCircuitBreaker, quickServiceCircuitBreaker);
//Fetch response from local resource
// Fetch response from local resource
LOGGER.info(monitoringService.localResourceResponse());
//Fetch response from delayed service 2 times, to meet the failure threshold
// Fetch response from delayed service 2 times, to meet the failure threshold
LOGGER.info(monitoringService.delayedServiceResponse());
LOGGER.info(monitoringService.delayedServiceResponse());
//Fetch current state of delayed service circuit breaker after crossing failure threshold limit
//which is OPEN now
// Fetch current state of delayed service circuit breaker after crossing failure threshold limit
// which is OPEN now
LOGGER.info(delayedServiceCircuitBreaker.getState());
//Meanwhile, the delayed service is down, fetch response from the healthy quick service
// Meanwhile, the delayed service is down, fetch response from the healthy quick service
LOGGER.info(monitoringService.quickServiceResponse());
LOGGER.info(quickServiceCircuitBreaker.getState());
//Wait for the delayed service to become responsive
// Wait for the delayed service to become responsive
try {
LOGGER.info("Waiting for delayed service to become responsive");
Thread.sleep(5000);
} catch (InterruptedException e) {
LOGGER.error("An error occurred: ", e);
}
//Check the state of delayed circuit breaker, should be HALF_OPEN
// Check the state of delayed circuit breaker, should be HALF_OPEN
LOGGER.info(delayedServiceCircuitBreaker.getState());
//Fetch response from delayed service, which should be healthy by now
// Fetch response from delayed service, which should be healthy by now
LOGGER.info(monitoringService.delayedServiceResponse());
//As successful response is fetched, it should be CLOSED again.
// As successful response is fetched, it should be CLOSED again.
LOGGER.info(delayedServiceCircuitBreaker.getState());
}
}
@@ -24,9 +24,7 @@
*/
package com.iluwatar.circuitbreaker;
/**
* The Circuit breaker interface.
*/
/** The Circuit breaker interface. */
public interface CircuitBreaker {
// Success response. Reset everything to defaults
@@ -45,14 +45,14 @@ public class DefaultCircuitBreaker implements CircuitBreaker {
/**
* Constructor to create an instance of Circuit Breaker.
*
* @param timeout Timeout for the API request. Not necessary for this simple example
* @param timeout Timeout for the API request. Not necessary for this simple example
* @param failureThreshold Number of failures we receive from the depended on service before
* changing state to 'OPEN'
* @param retryTimePeriod Time, in nanoseconds, period after which a new request is made to
* remote service for status check.
* changing state to 'OPEN'
* @param retryTimePeriod Time, in nanoseconds, period after which a new request is made to remote
* service for status check.
*/
DefaultCircuitBreaker(RemoteService serviceToCall, long timeout, int failureThreshold,
long retryTimePeriod) {
DefaultCircuitBreaker(
RemoteService serviceToCall, long timeout, int failureThreshold, long retryTimePeriod) {
this.service = serviceToCall;
// We start in a closed state hoping that everything is fine
this.state = State.CLOSED;
@@ -61,7 +61,7 @@ public class DefaultCircuitBreaker implements CircuitBreaker {
// Used to break the calls made to remote resource if it exceeds the limit
this.timeout = timeout;
this.retryTimePeriod = retryTimePeriod;
//An absurd amount of time in future which basically indicates the last failure never happened
// An absurd amount of time in future which basically indicates the last failure never happened
this.lastFailureTime = System.nanoTime() + futureTime;
this.failureCount = 0;
}
@@ -84,16 +84,16 @@ public class DefaultCircuitBreaker implements CircuitBreaker {
// Evaluate the current state based on failureThreshold, failureCount and lastFailureTime.
protected void evaluateState() {
if (failureCount >= failureThreshold) { //Then something is wrong with remote service
if (failureCount >= failureThreshold) { // Then something is wrong with remote service
if ((System.nanoTime() - lastFailureTime) > retryTimePeriod) {
//We have waited long enough and should try checking if service is up
// We have waited long enough and should try checking if service is up
state = State.HALF_OPEN;
} else {
//Service would still probably be down
// Service would still probably be down
state = State.OPEN;
}
} else {
//Everything is working fine
// Everything is working fine
state = State.CLOSED;
}
}
@@ -140,9 +140,9 @@ public class DefaultCircuitBreaker implements CircuitBreaker {
} else {
// Make the API request if the circuit is not OPEN
try {
//In a real application, this would be run in a thread and the timeout
//parameter of the circuit breaker would be utilized to know if service
//is working. Here, we simulate that based on server response itself
// In a real application, this would be run in a thread and the timeout
// parameter of the circuit breaker would be utilized to know if service
// is working. Here, we simulate that based on server response itself
var response = service.call();
// Yay!! the API responded fine. Let's reset everything.
recordSuccess();
@@ -56,12 +56,12 @@ public class DelayedRemoteService implements RemoteService {
@Override
public String call() throws RemoteServiceException {
var currentTime = System.nanoTime();
//Since currentTime and serverStartTime are both in nanoseconds, we convert it to
//seconds by diving by 10e9 and ensure floating point division by multiplying it
//with 1.0 first. We then check if it is greater or less than specified delay and then
//send the reply
// Since currentTime and serverStartTime are both in nanoseconds, we convert it to
// seconds by diving by 10e9 and ensure floating point division by multiplying it
// with 1.0 first. We then check if it is greater or less than specified delay and then
// send the reply
if ((currentTime - serverStartTime) * 1.0 / (1000 * 1000 * 1000) < delay) {
//Can use Thread.sleep() here to block and simulate a hung server
// Can use Thread.sleep() here to block and simulate a hung server
throw new RemoteServiceException("Delayed service is down");
}
return "Delayed service is working";
@@ -39,7 +39,7 @@ public class MonitoringService {
this.quickService = quickService;
}
//Assumption: Local service won't fail, no need to wrap it in a circuit breaker logic
// Assumption: Local service won't fail, no need to wrap it in a circuit breaker logic
public String localResourceResponse() {
return "Local Service is working";
}
@@ -69,4 +69,4 @@ public class MonitoringService {
return e.getMessage();
}
}
}
}
@@ -24,9 +24,7 @@
*/
package com.iluwatar.circuitbreaker;
/**
* A quick response remote service, that responds healthy without any delay or failure.
*/
/** A quick response remote service, that responds healthy without any delay or failure. */
public class QuickRemoteService implements RemoteService {
@Override
@@ -30,6 +30,6 @@ package com.iluwatar.circuitbreaker;
*/
public interface RemoteService {
//Fetch response from remote service.
// Fetch response from remote service.
String call() throws RemoteServiceException;
}
@@ -24,9 +24,7 @@
*/
package com.iluwatar.circuitbreaker;
/**
* Exception thrown when {@link RemoteService} does not respond successfully.
*/
/** Exception thrown when {@link RemoteService} does not respond successfully. */
public class RemoteServiceException extends Exception {
public RemoteServiceException(String message) {
@@ -24,11 +24,9 @@
*/
package com.iluwatar.circuitbreaker;
/**
* Enumeration for states the circuit breaker could be in.
*/
/** Enumeration for states the circuit breaker could be in. */
public enum State {
CLOSED,
OPEN,
HALF_OPEN
}
}
@@ -31,20 +31,18 @@ import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* App Test showing usage of circuit breaker.
*/
/** App Test showing usage of circuit breaker. */
class AppTest {
private static final Logger LOGGER = LoggerFactory.getLogger(AppTest.class);
//Startup delay for delayed service (in seconds)
// Startup delay for delayed service (in seconds)
private static final int STARTUP_DELAY = 4;
//Number of failed requests for circuit breaker to open
// Number of failed requests for circuit breaker to open
private static final int FAILURE_THRESHOLD = 1;
//Time period in seconds for circuit breaker to retry service
// Time period in seconds for circuit breaker to retry service
private static final int RETRY_PERIOD = 2;
private MonitoringService monitoringService;
@@ -62,75 +60,75 @@ class AppTest {
@BeforeEach
void setupCircuitBreakers() {
var delayedService = new DelayedRemoteService(System.nanoTime(), STARTUP_DELAY);
//Set the circuit Breaker parameters
delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000,
FAILURE_THRESHOLD,
RETRY_PERIOD * 1000 * 1000 * 1000);
// Set the circuit Breaker parameters
delayedServiceCircuitBreaker =
new DefaultCircuitBreaker(
delayedService, 3000, FAILURE_THRESHOLD, RETRY_PERIOD * 1000 * 1000 * 1000);
var quickService = new QuickRemoteService();
//Set the circuit Breaker parameters
quickServiceCircuitBreaker = new DefaultCircuitBreaker(quickService, 3000, FAILURE_THRESHOLD,
RETRY_PERIOD * 1000 * 1000 * 1000);
monitoringService = new MonitoringService(delayedServiceCircuitBreaker,
quickServiceCircuitBreaker);
// Set the circuit Breaker parameters
quickServiceCircuitBreaker =
new DefaultCircuitBreaker(
quickService, 3000, FAILURE_THRESHOLD, RETRY_PERIOD * 1000 * 1000 * 1000);
monitoringService =
new MonitoringService(delayedServiceCircuitBreaker, quickServiceCircuitBreaker);
}
@Test
void testFailure_OpenStateTransition() {
//Calling delayed service, which will be unhealthy till 4 seconds
// Calling delayed service, which will be unhealthy till 4 seconds
assertEquals("Delayed service is down", monitoringService.delayedServiceResponse());
//As failure threshold is "1", the circuit breaker is changed to OPEN
// As failure threshold is "1", the circuit breaker is changed to OPEN
assertEquals("OPEN", delayedServiceCircuitBreaker.getState());
//As circuit state is OPEN, we expect a quick fallback response from circuit breaker.
// As circuit state is OPEN, we expect a quick fallback response from circuit breaker.
assertEquals("Delayed service is down", monitoringService.delayedServiceResponse());
//Meanwhile, the quick service is responding and the circuit state is CLOSED
// Meanwhile, the quick service is responding and the circuit state is CLOSED
assertEquals("Quick Service is working", monitoringService.quickServiceResponse());
assertEquals("CLOSED", quickServiceCircuitBreaker.getState());
}
@Test
void testFailure_HalfOpenStateTransition() {
//Calling delayed service, which will be unhealthy till 4 seconds
// Calling delayed service, which will be unhealthy till 4 seconds
assertEquals("Delayed service is down", monitoringService.delayedServiceResponse());
//As failure threshold is "1", the circuit breaker is changed to OPEN
// As failure threshold is "1", the circuit breaker is changed to OPEN
assertEquals("OPEN", delayedServiceCircuitBreaker.getState());
//Waiting for recovery period of 2 seconds for circuit breaker to retry service.
// Waiting for recovery period of 2 seconds for circuit breaker to retry service.
try {
LOGGER.info("Waiting 2s for delayed service to become responsive");
Thread.sleep(2000);
} catch (InterruptedException e) {
LOGGER.error("An error occurred: ", e);
}
//After 2 seconds, the circuit breaker should move to "HALF_OPEN" state and retry fetching response from service again
// After 2 seconds, the circuit breaker should move to "HALF_OPEN" state and retry fetching
// response from service again
assertEquals("HALF_OPEN", delayedServiceCircuitBreaker.getState());
}
@Test
void testRecovery_ClosedStateTransition() {
//Calling delayed service, which will be unhealthy till 4 seconds
// Calling delayed service, which will be unhealthy till 4 seconds
assertEquals("Delayed service is down", monitoringService.delayedServiceResponse());
//As failure threshold is "1", the circuit breaker is changed to OPEN
// As failure threshold is "1", the circuit breaker is changed to OPEN
assertEquals("OPEN", delayedServiceCircuitBreaker.getState());
//Waiting for 4 seconds, which is enough for DelayedService to become healthy and respond successfully.
// Waiting for 4 seconds, which is enough for DelayedService to become healthy and respond
// successfully.
try {
LOGGER.info("Waiting 4s for delayed service to become responsive");
Thread.sleep(4000);
} catch (InterruptedException e) {
LOGGER.error("An error occurred: ", e);
}
//As retry period is 2 seconds (<4 seconds of wait), hence the circuit breaker should be back in HALF_OPEN state.
// As retry period is 2 seconds (<4 seconds of wait), hence the circuit breaker should be back
// in HALF_OPEN state.
assertEquals("HALF_OPEN", delayedServiceCircuitBreaker.getState());
//Check the success response from delayed service.
// Check the success response from delayed service.
assertEquals("Delayed service is working", monitoringService.delayedServiceResponse());
//As the response is success, the state should be CLOSED
// As the response is success, the state should be CLOSED
assertEquals("CLOSED", delayedServiceCircuitBreaker.getState());
}
}
@@ -28,29 +28,27 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
/**
* Circuit Breaker test
*/
/** Circuit Breaker test */
class DefaultCircuitBreakerTest {
//long timeout, int failureThreshold, long retryTimePeriod
// long timeout, int failureThreshold, long retryTimePeriod
@Test
void testEvaluateState() {
var circuitBreaker = new DefaultCircuitBreaker(null, 1, 1, 100);
//Right now, failureCount<failureThreshold, so state should be closed
// Right now, failureCount<failureThreshold, so state should be closed
assertEquals(circuitBreaker.getState(), "CLOSED");
circuitBreaker.failureCount = 4;
circuitBreaker.lastFailureTime = System.nanoTime();
circuitBreaker.evaluateState();
//Since failureCount>failureThreshold, and lastFailureTime is nearly equal to current time,
//state should be half-open
// Since failureCount>failureThreshold, and lastFailureTime is nearly equal to current time,
// state should be half-open
assertEquals(circuitBreaker.getState(), "HALF_OPEN");
//Since failureCount>failureThreshold, and lastFailureTime is much lesser current time,
//state should be open
// Since failureCount>failureThreshold, and lastFailureTime is much lesser current time,
// state should be open
circuitBreaker.lastFailureTime = System.nanoTime() - 1000 * 1000 * 1000 * 1000;
circuitBreaker.evaluateState();
assertEquals(circuitBreaker.getState(), "OPEN");
//Now set it back again to closed to test idempotency
// Now set it back again to closed to test idempotency
circuitBreaker.failureCount = 0;
circuitBreaker.evaluateState();
assertEquals(circuitBreaker.getState(), "CLOSED");
@@ -59,23 +57,24 @@ class DefaultCircuitBreakerTest {
@Test
void testSetStateForBypass() {
var circuitBreaker = new DefaultCircuitBreaker(null, 1, 1, 2000 * 1000 * 1000);
//Right now, failureCount<failureThreshold, so state should be closed
//Bypass it and set it to open
// Right now, failureCount<failureThreshold, so state should be closed
// Bypass it and set it to open
circuitBreaker.setState(State.OPEN);
assertEquals(circuitBreaker.getState(), "OPEN");
}
@Test
void testApiResponses() throws RemoteServiceException {
RemoteService mockService = new RemoteService() {
@Override
public String call() throws RemoteServiceException {
return "Remote Success";
}
};
RemoteService mockService =
new RemoteService() {
@Override
public String call() throws RemoteServiceException {
return "Remote Success";
}
};
var circuitBreaker = new DefaultCircuitBreaker(mockService, 1, 1, 100);
//Call with the parameter start_time set to huge amount of time in past so that service
//replies with "Ok". Also, state is CLOSED in start
// Call with the parameter start_time set to huge amount of time in past so that service
// replies with "Ok". Also, state is CLOSED in start
var serviceStartTime = System.nanoTime() - 60 * 1000 * 1000 * 1000;
var response = circuitBreaker.attemptRequest();
assertEquals(response, "Remote Success");
@@ -29,9 +29,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/**
* Monitoring Service test
*/
/** Monitoring Service test */
class DelayedRemoteServiceTest {
/**
@@ -41,10 +39,12 @@ class DelayedRemoteServiceTest {
*/
@Test
void testDefaultConstructor() throws RemoteServiceException {
Assertions.assertThrows(RemoteServiceException.class, () -> {
var obj = new DelayedRemoteService();
obj.call();
});
Assertions.assertThrows(
RemoteServiceException.class,
() -> {
var obj = new DelayedRemoteService();
obj.call();
});
}
/**
@@ -54,7 +54,7 @@ class DelayedRemoteServiceTest {
*/
@Test
void testParameterizedConstructor() throws RemoteServiceException {
var obj = new DelayedRemoteService(System.nanoTime()-2000*1000*1000,1);
assertEquals("Delayed service is working",obj.call());
var obj = new DelayedRemoteService(System.nanoTime() - 2000 * 1000 * 1000, 1);
assertEquals("Delayed service is working", obj.call());
}
}
@@ -28,28 +28,25 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
/**
* Monitoring Service test
*/
/** Monitoring Service test */
class MonitoringServiceTest {
//long timeout, int failureThreshold, long retryTimePeriod
// long timeout, int failureThreshold, long retryTimePeriod
@Test
void testLocalResponse() {
var monitoringService = new MonitoringService(null,null);
var monitoringService = new MonitoringService(null, null);
var response = monitoringService.localResourceResponse();
assertEquals(response, "Local Service is working");
}
@Test
void testDelayedRemoteResponseSuccess() {
var delayedService = new DelayedRemoteService(System.nanoTime()-2*1000*1000*1000, 2);
var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000,
1,
2 * 1000 * 1000 * 1000);
var delayedService = new DelayedRemoteService(System.nanoTime() - 2 * 1000 * 1000 * 1000, 2);
var delayedServiceCircuitBreaker =
new DefaultCircuitBreaker(delayedService, 3000, 1, 2 * 1000 * 1000 * 1000);
var monitoringService = new MonitoringService(delayedServiceCircuitBreaker,null);
//Set time in past to make the server work
var monitoringService = new MonitoringService(delayedServiceCircuitBreaker, null);
// Set time in past to make the server work
var response = monitoringService.delayedServiceResponse();
assertEquals(response, "Delayed service is working");
}
@@ -57,11 +54,10 @@ class MonitoringServiceTest {
@Test
void testDelayedRemoteResponseFailure() {
var delayedService = new DelayedRemoteService(System.nanoTime(), 2);
var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000,
1,
2 * 1000 * 1000 * 1000);
var monitoringService = new MonitoringService(delayedServiceCircuitBreaker,null);
//Set time as current time as initially server fails
var delayedServiceCircuitBreaker =
new DefaultCircuitBreaker(delayedService, 3000, 1, 2 * 1000 * 1000 * 1000);
var monitoringService = new MonitoringService(delayedServiceCircuitBreaker, null);
// Set time as current time as initially server fails
var response = monitoringService.delayedServiceResponse();
assertEquals(response, "Delayed service is down");
}
@@ -69,11 +65,10 @@ class MonitoringServiceTest {
@Test
void testQuickRemoteServiceResponse() {
var delayedService = new QuickRemoteService();
var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000,
1,
2 * 1000 * 1000 * 1000);
var monitoringService = new MonitoringService(delayedServiceCircuitBreaker,null);
//Set time as current time as initially server fails
var delayedServiceCircuitBreaker =
new DefaultCircuitBreaker(delayedService, 3000, 1, 2 * 1000 * 1000 * 1000);
var monitoringService = new MonitoringService(delayedServiceCircuitBreaker, null);
// Set time as current time as initially server fails
var response = monitoringService.delayedServiceResponse();
assertEquals(response, "Quick Service is working");
}