|
16 | 16 |
|
17 | 17 | import java.util.concurrent.CompletableFuture; |
18 | 18 | import java.util.concurrent.CompletionStage; |
| 19 | +import java.util.logging.Logger; |
19 | 20 |
|
| 21 | +/** |
| 22 | + * Payment service demonstrating MicroProfile Fault Tolerance patterns. |
| 23 | + * |
| 24 | + * Each method demonstrates specific fault tolerance strategies: |
| 25 | + * - authorizePayment: Retry + Timeout + Fallback for transient failures |
| 26 | + * - checkGatewayHealth: Circuit Breaker + Timeout to prevent hammering failed services |
| 27 | + * - sendPaymentNotification: Asynchronous + Bulkhead + Fallback for resource isolation |
| 28 | + * |
| 29 | + * These patterns work together to prevent cascading failures and ensure resilient payment processing. |
| 30 | + */ |
20 | 31 | @ApplicationScoped |
21 | 32 | public class PaymentService { |
22 | 33 |
|
23 | | - @ConfigProperty(name = "payment.gateway.endpoint", defaultValue = "https://defaultapi.paymentgateway.com") |
| 34 | + private static final Logger logger = Logger.getLogger(PaymentService.class.getName()); |
| 35 | + |
| 36 | + @ConfigProperty(name = "payment.gateway.endpoint", defaultValue = "https://api.paymentgateway.com") |
24 | 37 | private String endpoint; |
25 | 38 |
|
26 | 39 | /** |
27 | | - * Process the payment request. |
28 | | - * |
29 | | - * @param paymentDetails details of the payment |
30 | | - * @return response message indicating success or failure |
31 | | - * @throws PaymentProcessingException if a transient issue occurs |
| 40 | + * Authorize a payment transaction with fault tolerance. |
| 41 | + * |
| 42 | + * Fault Tolerance Strategy: |
| 43 | + * - @Retry: Handles transient network failures (up to 3 retries with jitter) |
| 44 | + * - @Timeout: Prevents indefinite waits (3 second limit per attempt) |
| 45 | + * - @Fallback: Provides degraded service when gateway unavailable |
| 46 | + * |
| 47 | + * @param paymentDetails Payment details to authorize |
| 48 | + * @return Authorization result JSON |
| 49 | + * @throws CriticalPaymentException For non-retryable failures (invalid card, insufficient funds) |
32 | 50 | */ |
33 | | - @Asynchronous |
| 51 | + @Retry( |
| 52 | + maxRetries = 3, |
| 53 | + delay = 2000, |
| 54 | + jitter = 500, |
| 55 | + retryOn = PaymentProcessingException.class, |
| 56 | + abortOn = CriticalPaymentException.class |
| 57 | + ) |
34 | 58 | @Timeout(3000) |
35 | | - @Retry(maxRetries = 3, |
36 | | - delay = 2000, |
37 | | - jitter = 500, |
38 | | - retryOn = PaymentProcessingException.class, |
39 | | - abortOn = CriticalPaymentException.class) |
40 | | - @Fallback(fallbackMethod = "fallbackProcessPayment") |
41 | | - @Bulkhead(value=5) |
| 59 | + @Fallback(fallbackMethod = "fallbackAuthorizePayment") |
| 60 | + public String authorizePayment(PaymentDetails paymentDetails) |
| 61 | + throws PaymentProcessingException { |
| 62 | + |
| 63 | + logger.info("Calling payment gateway at: " + endpoint); |
| 64 | + logger.info("Processing payment for amount: " + paymentDetails.getAmount()); |
| 65 | + |
| 66 | + // Simulate network latency |
| 67 | + simulateDelay(1500); |
| 68 | + |
| 69 | + // Simulate transient failures (60% failure rate to demonstrate retries) |
| 70 | + if (Math.random() > 0.4) { |
| 71 | + throw new PaymentProcessingException( |
| 72 | + "Temporary payment gateway failure - will retry" |
| 73 | + ); |
| 74 | + } |
| 75 | + |
| 76 | + return String.format( |
| 77 | + "{\"status\":\"success\",\"message\":\"Payment authorized\",\"amount\":%s}", |
| 78 | + paymentDetails.getAmount() |
| 79 | + ); |
| 80 | + } |
| 81 | + |
| 82 | + /** |
| 83 | + * Fallback method for payment authorization. |
| 84 | + * Returns a pending status and queues payment for offline processing. |
| 85 | + * |
| 86 | + * @param paymentDetails Payment details |
| 87 | + * @return Fallback response indicating payment is queued |
| 88 | + */ |
| 89 | + public String fallbackAuthorizePayment(PaymentDetails paymentDetails) { |
| 90 | + logger.warning("Payment gateway unavailable - using fallback for amount: " |
| 91 | + + paymentDetails.getAmount()); |
| 92 | + |
| 93 | + // In production: queue for offline processing, use backup gateway, etc. |
| 94 | + return String.format( |
| 95 | + "{\"status\":\"pending\",\"message\":\"Payment queued for processing\",\"amount\":%s}", |
| 96 | + paymentDetails.getAmount() |
| 97 | + ); |
| 98 | + } |
| 99 | + |
| 100 | + /** |
| 101 | + * Check payment gateway health with circuit breaker protection. |
| 102 | + * |
| 103 | + * Fault Tolerance Strategy: |
| 104 | + * - @CircuitBreaker: Prevents hammering failed gateway (opens at 50% failure rate) |
| 105 | + * - @Timeout: Quick health check (2 second limit) |
| 106 | + * |
| 107 | + * Circuit breaker will OPEN after 2 failures out of 4 requests (50% failure ratio), |
| 108 | + * wait 5 seconds in OPEN state, then transition to HALF_OPEN to test recovery. |
| 109 | + * Two consecutive successes in HALF_OPEN state will CLOSE the circuit. |
| 110 | + * |
| 111 | + * @return true if gateway is healthy, false otherwise |
| 112 | + */ |
42 | 113 | @CircuitBreaker( |
43 | 114 | failureRatio = 0.5, |
44 | 115 | requestVolumeThreshold = 4, |
45 | | - delay = 3000 |
| 116 | + delay = 5000, |
| 117 | + successThreshold = 2 |
46 | 118 | ) |
47 | | - public CompletionStage<String> processPayment(PaymentDetails paymentDetails) throws PaymentProcessingException { |
| 119 | + @Timeout(2000) |
| 120 | + public boolean checkGatewayHealth() { |
| 121 | + logger.info("Checking payment gateway health"); |
48 | 122 |
|
49 | | - // Example logic to call the payment gateway API |
50 | | - System.out.println("Calling payment gateway API at: " + endpoint); |
51 | | - |
52 | | - simulateDelay(); |
53 | | - |
54 | | - System.out.println("Processing payment for amount: " + paymentDetails.getAmount()); |
55 | | - |
56 | | - // Simulating a transient failure |
57 | | - if (Math.random() > 0.7) { |
58 | | - throw new PaymentProcessingException("Temporary payment processing failure"); |
| 123 | + // Simulate health check call |
| 124 | + simulateDelay(500); |
| 125 | + |
| 126 | + // Simulate intermittent gateway failures (60% success rate) |
| 127 | + if (Math.random() > 0.6) { |
| 128 | + throw new RuntimeException("Gateway health check failed"); |
59 | 129 | } |
| 130 | + |
| 131 | + return true; |
| 132 | + } |
60 | 133 |
|
61 | | - // Simulating successful processing |
62 | | - return CompletableFuture.completedFuture("{\"status\":\"success\", \"message\":\"Payment processed successfully.\"}"); |
| 134 | + /** |
| 135 | + * Send payment notification asynchronously with resource isolation. |
| 136 | + * |
| 137 | + * Fault Tolerance Strategy: |
| 138 | + * - @Asynchronous: Non-blocking execution in separate thread |
| 139 | + * - @Bulkhead: Limits concurrent notifications (max 10 concurrent, queue up to 20) |
| 140 | + * - @Timeout: Prevents stuck notification threads (5 second limit) |
| 141 | + * - @Fallback: Logs failed notifications for later retry |
| 142 | + * |
| 143 | + * Uses CompletionStage (not Future) to ensure fault tolerance annotations |
| 144 | + * react properly to asynchronous failures. With CompletionStage, exceptions |
| 145 | + * in exceptionally-completed stages trigger @Retry, @CircuitBreaker, and @Fallback. |
| 146 | + * |
| 147 | + * @param paymentId Payment identifier |
| 148 | + * @param recipient Notification recipient email/phone |
| 149 | + * @return CompletionStage that completes when notification is sent |
| 150 | + */ |
| 151 | + @Asynchronous |
| 152 | + @Bulkhead(value = 10, waitingTaskQueue = 20) |
| 153 | + @Timeout(5000) |
| 154 | + @Fallback(fallbackMethod = "fallbackSendNotification") |
| 155 | + public CompletionStage<String> sendPaymentNotification( |
| 156 | + String paymentId, |
| 157 | + String recipient |
| 158 | + ) { |
| 159 | + logger.info("Notification queued for payment: " + paymentId + " to " + recipient); |
| 160 | + |
| 161 | + // Schedule actual notification work in background (fire-and-forget) |
| 162 | + // This allows the HTTP response to return immediately |
| 163 | + CompletableFuture.runAsync(() -> { |
| 164 | + try { |
| 165 | + // Simulate notification sending delay (e.g., calling external SMS/email service) |
| 166 | + simulateDelay(2000); |
| 167 | + |
| 168 | + // Simulate notification failures (80% success rate) |
| 169 | + if (Math.random() > 0.8) { |
| 170 | + logger.warning("Notification service unavailable for payment: " + paymentId); |
| 171 | + throw new RuntimeException("Notification service unavailable"); |
| 172 | + } |
| 173 | + |
| 174 | + logger.info("Notification sent successfully for payment: " + paymentId); |
| 175 | + } catch (Exception e) { |
| 176 | + logger.severe("Failed to send notification for payment: " + paymentId + " - " + e.getMessage()); |
| 177 | + } |
| 178 | + }); |
| 179 | + |
| 180 | + // Return immediately - client gets instant response |
| 181 | + // @Asynchronous annotation ensures this executes on a background thread, |
| 182 | + // but the CompletionStage itself completes immediately |
| 183 | + return CompletableFuture.completedFuture("Notification queued for processing"); |
63 | 184 | } |
64 | 185 |
|
65 | 186 | /** |
66 | | - * Fallback method when payment processing fails. |
67 | | - * |
68 | | - * @param paymentDetails details of the payment |
69 | | - * @return response message for fallback |
| 187 | + * Fallback for notification - logs failure for later retry. |
| 188 | + * |
| 189 | + * @param paymentId Payment identifier |
| 190 | + * @param recipient Notification recipient |
| 191 | + * @return CompletionStage indicating notification was queued |
70 | 192 | */ |
71 | | - public CompletionStage<String> fallbackProcessPayment(PaymentDetails paymentDetails) { |
72 | | - System.out.println("Fallback invoked for payment of amount: " + paymentDetails.getAmount()); |
73 | | - return CompletableFuture.completedFuture("{\"status\":\"failed\", \"message\":\"Payment service is currently unavailable.\"}"); |
| 193 | + public CompletionStage<String> fallbackSendNotification( |
| 194 | + String paymentId, |
| 195 | + String recipient |
| 196 | + ) { |
| 197 | + logger.warning("Failed to send notification for payment: " + paymentId); |
| 198 | + |
| 199 | + // In production: queue notification for retry, use backup channel (SMS if email failed), etc. |
| 200 | + return CompletableFuture.completedFuture( |
| 201 | + "Notification queued for retry" |
| 202 | + ); |
74 | 203 | } |
75 | 204 |
|
76 | 205 | /** |
77 | | - * Simulate a delay in processing to demonstrate timeout. |
| 206 | + * Simulate network/processing delay. |
| 207 | + * |
| 208 | + * @param milliseconds Delay duration in milliseconds |
78 | 209 | */ |
79 | | - private void simulateDelay() { |
| 210 | + private void simulateDelay(long milliseconds) { |
80 | 211 | try { |
81 | | - Thread.sleep(1500); // Simulated long-running task |
| 212 | + Thread.sleep(milliseconds); |
82 | 213 | } catch (InterruptedException e) { |
83 | 214 | Thread.currentThread().interrupt(); |
84 | | - throw new RuntimeException("Processing interrupted"); |
| 215 | + throw new RuntimeException("Processing interrupted", e); |
85 | 216 | } |
86 | 217 | } |
87 | 218 | } |
0 commit comments