View Javadoc
1   package com.guinetik.examples;
2   
3   import com.guinetik.rr.RocketRest;
4   import com.guinetik.rr.RocketRestConfig;
5   import com.guinetik.rr.http.CircuitBreakerOpenException;
6   import com.guinetik.rr.http.HttpConstants;
7   import com.guinetik.rr.result.ApiError;
8   import com.guinetik.rr.result.Result;
9   
10  import java.time.LocalDateTime;
11  
12  /**
13   * Example demonstrating the Circuit Breaker pattern using RocketRest
14   * with https://httpstat.us/ as a test service.
15   */
16  public class RocketRestCircuitBreakerExample implements Example {
17      
18      private static final String API_BASE_URL = "https://httpstat.us";
19      private static final int CIRCUIT_FAILURE_THRESHOLD = 3;
20      private static final long CIRCUIT_RESET_TIMEOUT_MS = 5000; // 5 seconds
21      
22      @Override
23      public String getName() {
24          return "RocketRest Circuit Breaker Pattern";
25      }
26      
27      @Override
28      public void run() {
29          System.out.println("Demonstrating RocketRest with Circuit Breaker pattern...");
30          
31          // Create configuration for the API with circuit breaker settings
32          RocketRestConfig config = RocketRestConfig.builder(API_BASE_URL)
33                  .defaultOptions(options -> {
34                      // Configure circuit breaker options
35                      options.set(HttpConstants.CircuitBreaker.CIRCUIT_BREAKER_ENABLED, true);
36                      options.set(HttpConstants.CircuitBreaker.CIRCUIT_BREAKER_FAILURE_THRESHOLD, CIRCUIT_FAILURE_THRESHOLD);
37                      options.set(HttpConstants.CircuitBreaker.CIRCUIT_BREAKER_RESET_TIMEOUT_MS, CIRCUIT_RESET_TIMEOUT_MS);
38                      options.set(HttpConstants.CircuitBreaker.CIRCUIT_BREAKER_FAILURE_POLICY, HttpConstants.CircuitBreaker.CIRCUIT_BREAKER_POLICY_SERVER_ONLY);
39                  })
40                  .build();
41          
42          // Create RocketRest client with circuit breaker configured
43          RocketRest client = new RocketRest(API_BASE_URL, config);
44          
45          try {
46              // Basic circuit breaker demonstration
47              demonstrateBasicCircuitBreaker(client);
48              
49              // Configure another client to show switching between 500 and 200
50              demonstrateCircuitBreakerRecovery(client);
51              
52          } finally {
53              client.shutdown();
54          }
55          
56          System.out.println("\nCircuit Breaker example completed.");
57      }
58      
59      /**
60       * Demonstrates the circuit breaker pattern with a RocketRest client
61       */
62      private void demonstrateBasicCircuitBreaker(RocketRest client) {
63          System.out.println("\n=== Basic Circuit Breaker Demonstration ===");
64          
65          // Make a successful request first
66          System.out.println("\n1. Making successful requests (200 status)...");
67          try {
68              // Success cases
69              for (int i = 0; i < 2; i++) {
70                  System.out.println(LocalDateTime.now() + " - Requesting 200 OK");
71                  String response = client.get("/200", String.class);
72                  System.out.println("✅ Success response: " + response);
73                  Thread.sleep(500);
74              }
75          } catch (Exception e) {
76              System.out.println("❌ Unexpected error: " + e.getMessage());
77          }
78          
79          // Now trigger the circuit breaker with error responses
80          System.out.println("\n2. Triggering circuit breaker with 500 errors...");
81          try {
82              // Cause failures to trip the circuit breaker
83              for (int i = 0; i < CIRCUIT_FAILURE_THRESHOLD + 1; i++) {
84                  try {
85                      System.out.println(LocalDateTime.now() + " - Requesting 500 Internal Server Error");
86                      // Use the fluent API to get a Result object for better error handling
87                      Result<String, ApiError> result = client.fluent().get("/500", String.class);
88                      
89                      if (result.isSuccess()) {
90                          System.out.println("✅ Got response: " + result.getValue());
91                      } else {
92                          ApiError error = result.getError();
93                          System.out.println("❌ Error (failure #" + (i+1) + "): " + error.getMessage() + 
94                                  " (Status: " + error.getStatusCode() + ")");
95                      }
96                  } catch (CircuitBreakerOpenException e) {
97                      System.out.println("⚡ Circuit breaker is now OPEN: " + e.getMessage());
98                      // The CircuitBreakerOpenException includes information about the reset timeout
99                      long millisUntilReset = getTimeUntilReset(e);
100                     System.out.println("Circuit will reset in approximately: " + millisUntilReset + "ms");
101                     break;
102                 } catch (Exception e) {
103                     System.out.println("❌ Error: " + e.getMessage());
104                 }
105                 
106                 Thread.sleep(500);
107             }
108             
109             // Circuit should be open now, so requests should fail fast
110             System.out.println("\n3. Testing fast-fail with circuit open...");
111             for (int i = 0; i < 3; i++) {
112                 try {
113                     System.out.println(LocalDateTime.now() + " - Attempting request when circuit is OPEN");
114                     client.get("/200", String.class); // This should fail fast if circuit is open
115                     System.out.println("✅ Request succeeded (circuit might be closed)");
116                 } catch (CircuitBreakerOpenException e) {
117                     System.out.println("⚡ Fast fail! Circuit is still OPEN: " + e.getMessage());
118                     long millisUntilReset = getTimeUntilReset(e);
119                     System.out.println("Circuit will reset in approximately: " + millisUntilReset + "ms");
120                 } catch (Exception e) {
121                     System.out.println("❌ Different error: " + e.getMessage());
122                 }
123                 
124                 Thread.sleep(1000);
125             }
126             
127             // Wait for the circuit to enter half-open state
128             System.out.println("\n4. Waiting for circuit reset timeout (" + CIRCUIT_RESET_TIMEOUT_MS + "ms)...");
129             Thread.sleep(CIRCUIT_RESET_TIMEOUT_MS);
130             
131             // Now make a successful request to close the circuit
132             System.out.println("\n5. Circuit should be HALF-OPEN now, trying to close it with successful requests...");
133             for (int i = 0; i < 2; i++) {
134                 try {
135                     System.out.println(LocalDateTime.now() + " - Requesting 200 OK to close circuit");
136                     String response = client.get("/200", String.class);
137                     System.out.println("✅ Success! Response: " + response);
138                     System.out.println("Circuit should be CLOSED now");
139                 } catch (CircuitBreakerOpenException e) {
140                     System.out.println("⚡ Circuit is still OPEN: " + e.getMessage());
141                 } catch (Exception e) {
142                     System.out.println("❌ Error: " + e.getMessage());
143                 }
144                 
145                 Thread.sleep(500);
146             }
147             
148         } catch (InterruptedException e) {
149             Thread.currentThread().interrupt();
150             System.out.println("Demo interrupted");
151         }
152     }
153     
154     /**
155      * Demonstrates alternating between 500 errors and 200 success to show the circuit breaker recovery
156      */
157     private void demonstrateCircuitBreakerRecovery(RocketRest client) {
158         System.out.println("\n=== Circuit Breaker Recovery Demonstration ===");
159         System.out.println("This example shows how the circuit breaker pattern helps with temporary service outages");
160         
161         try {
162             // Step 1: Start with successful requests to ensure circuit is closed
163             System.out.println("\n1. Starting with circuit CLOSED (successful requests)");
164             for (int i = 0; i < 2; i++) {
165                 try {
166                     String response = client.get("/200", String.class);
167                     System.out.println("✅ Success response: " + response);
168                 } catch (Exception e) {
169                     System.out.println("❌ Unexpected error: " + e.getMessage());
170                 }
171                 Thread.sleep(500);
172             }
173             
174             // Step 2: Trigger failures to open the circuit
175             System.out.println("\n2. Simulating a service outage with 500 errors");
176             for (int i = 0; i < CIRCUIT_FAILURE_THRESHOLD; i++) {
177                 try {
178                     client.get("/500", String.class);
179                     System.out.println("Request somehow succeeded (unexpected)");
180                 } catch (CircuitBreakerOpenException e) {
181                     System.out.println("⚡ Circuit breaker opened after " + (i+1) + " failures: " + e.getMessage());
182                     break;
183                 } catch (Exception e) {
184                     System.out.println("Error #" + (i+1) + ": " + e.getMessage());
185                 }
186                 Thread.sleep(500);
187             }
188             
189             // Step 3: Verify that circuit is open by fast-failing
190             System.out.println("\n3. Verifying circuit is OPEN (requests should fail fast)");
191             try {
192                 client.get("/200", String.class);
193                 System.out.println("Circuit might not be open yet");
194             } catch (CircuitBreakerOpenException e) {
195                 System.out.println("⚡ Circuit is open as expected: " + e.getMessage());
196             } catch (Exception e) {
197                 System.out.println("❌ Unexpected error type: " + e.getMessage());
198             }
199             
200             // Step 4: Wait for reset timeout
201             System.out.println("\n4. Waiting for circuit reset timeout to move to HALF-OPEN state...");
202             Thread.sleep(CIRCUIT_RESET_TIMEOUT_MS);
203             
204             // Step 5: Service is now "recovered" (switch to 200 OK)
205             System.out.println("\n5. Service has recovered (returning 200 OK)");
206             try {
207                 String response = client.get("/200", String.class);
208                 System.out.println("✅ Success! Circuit should move from HALF-OPEN to CLOSED");
209                 System.out.println("Response: " + response);
210             } catch (CircuitBreakerOpenException e) {
211                 System.out.println("⚡ Circuit is still open (unexpected): " + e.getMessage());
212             } catch (Exception e) {
213                 System.out.println("❌ Error: " + e.getMessage());
214             }
215             
216             // Step 6: Verify circuit is closed with more successful requests
217             System.out.println("\n6. Verifying circuit is CLOSED with more requests");
218             for (int i = 0; i < 3; i++) {
219                 try {
220                     String response = client.get("/200", String.class);
221                     System.out.println("✅ Success #" + (i+1) + ": " + response);
222                 } catch (Exception e) {
223                     System.out.println("❌ Error (unexpected): " + e.getMessage());
224                 }
225                 Thread.sleep(500);
226             }
227             
228         } catch (InterruptedException e) {
229             Thread.currentThread().interrupt();
230             System.out.println("Demo interrupted");
231         }
232     }
233     
234     /**
235      * Helper method to extract reset timeout information from the exception.
236      */
237     private long getTimeUntilReset(CircuitBreakerOpenException e) {
238         // Use the existing getEstimatedMillisUntilReset method
239         long timeUntilReset = e.getEstimatedMillisUntilReset();
240         
241         // If the existing method returns 0 (meaning it couldn't determine),
242         // then fall back to our configured timeout
243         return timeUntilReset > 0 ? timeUntilReset : CIRCUIT_RESET_TIMEOUT_MS;
244     }
245 }