@@ -52,40 +52,105 @@ In terms of code, the end user application is:
5252
5353``` java
5454public class App {
55-
55+
5656 private static final Logger LOGGER = LoggerFactory . getLogger(App . class);
57-
57+
58+ /**
59+ * Program entry point.
60+ *
61+ * @param args command line args
62+ */
5863 public static void main (String [] args ) {
59- var obj = new MonitoringService ();
60- var circuitBreaker = new CircuitBreaker (3000 , 1 , 2000 * 1000 * 1000 );
64+
6165 var serverStartTime = System . nanoTime();
62- while (true ) {
63- LOGGER . info(obj. localResourceResponse());
64- LOGGER . info(obj. remoteResourceResponse(circuitBreaker, serverStartTime));
65- LOGGER . info(circuitBreaker. getState());
66- try {
67- Thread . sleep(5 * 1000 );
68- } catch (InterruptedException e) {
69- LOGGER . error(e. getMessage());
70- }
66+
67+ var delayedService = new DelayedRemoteService (serverStartTime, 5 );
68+ // Set the circuit Breaker parameters
69+ var delayedServiceCircuitBreaker = new DefaultCircuitBreaker (delayedService, 3000 , 2 ,
70+ 2000 * 1000 * 1000 );
71+
72+ var quickService = new QuickRemoteService ();
73+ // Set the circuit Breaker parameters
74+ var quickServiceCircuitBreaker = new DefaultCircuitBreaker (quickService, 3000 , 2 ,
75+ 2000 * 1000 * 1000 );
76+
77+ // Create an object of monitoring service which makes both local and remote calls
78+ var monitoringService = new MonitoringService (delayedServiceCircuitBreaker,
79+ quickServiceCircuitBreaker);
80+
81+ // Fetch response from local resource
82+ LOGGER . info(monitoringService. localResourceResponse());
83+
84+ // Fetch response from delayed service 2 times, to meet the failure threshold
85+ LOGGER . info(monitoringService. delayedServiceResponse());
86+ LOGGER . info(monitoringService. delayedServiceResponse());
87+
88+ // Fetch current state of delayed service circuit breaker after crossing failure threshold limit
89+ // which is OPEN now
90+ LOGGER . info(delayedServiceCircuitBreaker. getState());
91+
92+ // Meanwhile, the delayed service is down, fetch response from the healthy quick service
93+ LOGGER . info(monitoringService. quickServiceResponse());
94+ LOGGER . info(quickServiceCircuitBreaker. getState());
95+
96+ // Wait for the delayed service to become responsive
97+ try {
98+ Thread . sleep(5000 );
99+ } catch (InterruptedException e) {
100+ e. printStackTrace();
71101 }
102+ // Check the state of delayed circuit breaker, should be HALF_OPEN
103+ LOGGER . info(delayedServiceCircuitBreaker. getState());
104+
105+ // Fetch response from delayed service, which should be healthy by now
106+ LOGGER . info(monitoringService. delayedServiceResponse());
107+ // As successful response is fetched, it should be CLOSED again.
108+ LOGGER . info(delayedServiceCircuitBreaker. getState());
72109 }
73110}
74111```
75112
76113The monitoring service:
77114
78- ``` java
115+ ``` java
79116public class MonitoringService {
80117
118+ private final CircuitBreaker delayedService;
119+
120+ private final CircuitBreaker quickService;
121+
122+ public MonitoringService (CircuitBreaker delayedService , CircuitBreaker quickService ) {
123+ this . delayedService = delayedService;
124+ this . quickService = quickService;
125+ }
126+
127+ // Assumption: Local service won't fail, no need to wrap it in a circuit breaker logic
81128 public String localResourceResponse () {
82129 return " Local Service is working" ;
83130 }
84131
85- public String remoteResourceResponse (CircuitBreaker circuitBreaker , long serverStartTime ) {
132+ /**
133+ * Fetch response from the delayed service (with some simulated startup time).
134+ *
135+ * @return response string
136+ */
137+ public String delayedServiceResponse () {
86138 try {
87- return circuitBreaker. call(" delayedService" , serverStartTime);
88- } catch (Exception e) {
139+ return this . delayedService. attemptRequest();
140+ } catch (RemoteServiceException e) {
141+ return e. getMessage();
142+ }
143+ }
144+
145+ /**
146+ * Fetches response from a healthy service without any failure.
147+ *
148+ * @return response string
149+ */
150+ public String quickServiceResponse () {
151+ try {
152+ return this . quickService. attemptRequest();
153+ } catch (RemoteServiceException e) {
89154 return e. getMessage();
90155 }
91156 }
@@ -95,76 +160,129 @@ As it can be seen, it does the call to get local resources directly, but it wrap
95160remote (costly) service in a circuit breaker object, which prevents faults as follows:
96161
97162``` java
98- public class CircuitBreaker {
163+ public class DefaultCircuitBreaker implements CircuitBreaker {
164+
99165 private final long timeout;
100166 private final long retryTimePeriod;
167+ private final RemoteService service;
101168 long lastFailureTime;
102169 int failureCount;
103170 private final int failureThreshold;
104171 private State state;
105172 private final long futureTime = 1000 * 1000 * 1000 * 1000 ;
106173
107- CircuitBreaker (long timeout , int failureThreshold , long retryTimePeriod ) {
174+ /**
175+ * Constructor to create an instance of Circuit Breaker.
176+ *
177+ * @param timeout Timeout for the API request. Not necessary for this simple example
178+ * @param failureThreshold Number of failures we receive from the depended service before changing
179+ * state to 'OPEN'
180+ * @param retryTimePeriod Time period after which a new request is made to remote service for
181+ * status check.
182+ */
183+ DefaultCircuitBreaker (RemoteService serviceToCall , long timeout , int failureThreshold ,
184+ long retryTimePeriod ) {
185+ this . service = serviceToCall;
186+ // We start in a closed state hoping that everything is fine
108187 this . state = State . CLOSED ;
109188 this . failureThreshold = failureThreshold;
189+ // Timeout for the API request.
190+ // Used to break the calls made to remote resource if it exceeds the limit
110191 this . timeout = timeout;
111192 this . retryTimePeriod = retryTimePeriod;
193+ // An absurd amount of time in future which basically indicates the last failure never happened
112194 this . lastFailureTime = System . nanoTime() + futureTime;
113195 this . failureCount = 0 ;
114196 }
115-
116- private void reset () {
197+
198+ // Reset everything to defaults
199+ @Override
200+ public void recordSuccess () {
117201 this . failureCount = 0 ;
118- this . lastFailureTime = System . nanoTime() + futureTime;
202+ this . lastFailureTime = System . nanoTime() + futureTime;
119203 this . state = State . CLOSED ;
120204 }
121205
122- private void recordFailure () {
206+ @Override
207+ public void recordFailure () {
123208 failureCount = failureCount + 1 ;
124209 this . lastFailureTime = System . nanoTime();
125210 }
126-
127- protected void setState () {
128- if (failureCount > failureThreshold) {
211+
212+ // Evaluate the current state based on failureThreshold, failureCount and lastFailureTime.
213+ protected void evaluateState () {
214+ if (failureCount >= failureThreshold) { // Then something is wrong with remote service
129215 if ((System . nanoTime() - lastFailureTime) > retryTimePeriod) {
216+ // We have waited long enough and should try checking if service is up
130217 state = State . HALF_OPEN ;
131218 } else {
219+ // Service would still probably be down
132220 state = State . OPEN ;
133221 }
134222 } else {
223+ // Everything is working fine
135224 state = State . CLOSED ;
136225 }
137226 }
138-
227+
228+ @Override
139229 public String getState () {
230+ evaluateState();
140231 return state. name();
141232 }
142-
143- public void setStateForBypass (State state ) {
233+
234+ /**
235+ * Break the circuit beforehand if it is known service is down Or connect the circuit manually if
236+ * service comes online before expected.
237+ *
238+ * @param state State at which circuit is in
239+ */
240+ @Override
241+ public void setState (State state ) {
144242 this . state = state;
243+ switch (state) {
244+ case OPEN :
245+ this . failureCount = failureThreshold;
246+ this . lastFailureTime = System . nanoTime();
247+ break ;
248+ case HALF_OPEN :
249+ this . failureCount = failureThreshold;
250+ this . lastFailureTime = System . nanoTime() - retryTimePeriod;
251+ break ;
252+ default :
253+ this . failureCount = 0 ;
254+ }
145255 }
146-
147- public String call (String serviceToCall , long serverStartTime ) throws Exception {
148- setState();
256+
257+ /**
258+ * Executes service call.
259+ *
260+ * @return Value from the remote resource, stale response or a custom exception
261+ */
262+ @Override
263+ public String attemptRequest () throws RemoteServiceException {
264+ evaluateState();
149265 if (state == State . OPEN ) {
266+ // return cached response if no the circuit is in OPEN state
150267 return " This is stale response from API" ;
151268 } else {
152- if (serviceToCall . equals( " delayedService " )) {
153- var delayedService = new DelayedService ( 20 );
154- var response = delayedService . response(serverStartTime);
155- if (response . split( " " )[ 3 ] . equals( " working " )) {
156- reset();
157- return response ;
158- } else {
159- recordFailure ();
160- throw new Exception ( " Remote service not responding " ) ;
161- }
162- } else {
163- throw new Exception ( " Unknown Service Name " ) ;
269+ // Make the API request if the circuit is not OPEN
270+ try {
271+ // In a real application, this would be run in a thread and the timeout
272+ // parameter of the circuit breaker would be utilized to know if service
273+ // is working. Here, we simulate that based on server response itself
274+ var response = service . call() ;
275+ // Yay!! the API responded fine. Let's reset everything.
276+ recordSuccess ();
277+ return response ;
278+ } catch ( RemoteServiceException ex) {
279+ recordFailure();
280+ throw ex ;
164281 }
165282 }
166283 }
167284}
285+
168286```
169287
170288How does the above pattern prevent failures? Let's understand via this finite state machine
0 commit comments