Skip to content

Commit 22db6b9

Browse files
authored
Refactor PaymentService for fault tolerance and logging
Enhanced PaymentService with detailed fault tolerance strategies and logging.
1 parent 8c5888a commit 22db6b9

File tree

1 file changed

+170
-39
lines changed
  • code/chapter08/payment/src/main/java/io/microprofile/tutorial/store/payment/service

1 file changed

+170
-39
lines changed

code/chapter08/payment/src/main/java/io/microprofile/tutorial/store/payment/service/PaymentService.java

Lines changed: 170 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -16,72 +16,203 @@
1616

1717
import java.util.concurrent.CompletableFuture;
1818
import java.util.concurrent.CompletionStage;
19+
import java.util.logging.Logger;
1920

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+
*/
2031
@ApplicationScoped
2132
public class PaymentService {
2233

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")
2437
private String endpoint;
2538

2639
/**
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)
3250
*/
33-
@Asynchronous
51+
@Retry(
52+
maxRetries = 3,
53+
delay = 2000,
54+
jitter = 500,
55+
retryOn = PaymentProcessingException.class,
56+
abortOn = CriticalPaymentException.class
57+
)
3458
@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+
*/
42113
@CircuitBreaker(
43114
failureRatio = 0.5,
44115
requestVolumeThreshold = 4,
45-
delay = 3000
116+
delay = 5000,
117+
successThreshold = 2
46118
)
47-
public CompletionStage<String> processPayment(PaymentDetails paymentDetails) throws PaymentProcessingException {
119+
@Timeout(2000)
120+
public boolean checkGatewayHealth() {
121+
logger.info("Checking payment gateway health");
48122

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");
59129
}
130+
131+
return true;
132+
}
60133

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");
63184
}
64185

65186
/**
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
70192
*/
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+
);
74203
}
75204

76205
/**
77-
* Simulate a delay in processing to demonstrate timeout.
206+
* Simulate network/processing delay.
207+
*
208+
* @param milliseconds Delay duration in milliseconds
78209
*/
79-
private void simulateDelay() {
210+
private void simulateDelay(long milliseconds) {
80211
try {
81-
Thread.sleep(1500); // Simulated long-running task
212+
Thread.sleep(milliseconds);
82213
} catch (InterruptedException e) {
83214
Thread.currentThread().interrupt();
84-
throw new RuntimeException("Processing interrupted");
215+
throw new RuntimeException("Processing interrupted", e);
85216
}
86217
}
87218
}

0 commit comments

Comments
 (0)