Skip to content

Commit e968703

Browse files
authored
fix: Repair fault tolerance config and bulkhead test for chapter08 payment service
- Restore PaymentResource to non-blocking CompletionStage<Response> pattern; remove .get() call that was blocking the JAX-RS request thread - Wrap paymentService.processPayment() call in try-catch to satisfy the Java compiler's checked-exception requirement for PaymentProcessingException - Introduce PaymentFallbackHandler (FallbackHandler<CompletionStage<String>>) to distinguish BulkheadException from other failures, returning "rejected" vs "failed" status — replaces the unsupported Throwable fallback method param - Set @bulkhead(value=5, waitingTaskQueue=2) so 10 concurrent requests trigger 3 rejections, making the bulkhead demo observable - Tighten @CIRCUITBREAKER thresholds (failureRatio=0.75, requestVolumeThreshold=10) so the 30% failure simulation never trips the circuit during the test - Fix test-payment-bulkhead.sh: correct log path, fix response-body detection strings ("rejected"/"failed"), display background output after wait, and grep temp files (not the non-existent /tmp/bulkhead.log) for result counting - Bump liberty-maven-plugin to 3.12.0
1 parent 9b29258 commit e968703

6 files changed

Lines changed: 98 additions & 64 deletions

File tree

code/chapter08/payment/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
<plugin>
7070
<groupId>io.openliberty.tools</groupId>
7171
<artifactId>liberty-maven-plugin</artifactId>
72-
<version>3.11.2</version>
72+
<version>3.12.0</version>
7373
<configuration>
7474
<serverName>mpServer</serverName>
7575
</configuration>
Lines changed: 45 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
package io.microprofile.tutorial.store.payment.resource;
22

3-
import org.eclipse.microprofile.config.inject.ConfigProperty;
43
import org.eclipse.microprofile.openapi.annotations.Operation;
4+
import org.eclipse.microprofile.openapi.annotations.media.Content;
5+
import org.eclipse.microprofile.openapi.annotations.media.Schema;
56
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
67
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
78

89
import io.microprofile.tutorial.store.payment.entity.PaymentDetails;
9-
import io.microprofile.tutorial.store.payment.exception.CriticalPaymentException;
1010
import io.microprofile.tutorial.store.payment.exception.PaymentProcessingException;
1111
import io.microprofile.tutorial.store.payment.service.PaymentService;
1212
import jakarta.enterprise.context.RequestScoped;
@@ -25,10 +25,6 @@
2525
@RequestScoped
2626
@Path("/")
2727
public class PaymentResource {
28-
29-
@Inject
30-
@ConfigProperty(name = "payment.gateway.endpoint")
31-
private String endpoint;
3228

3329
@Inject
3430
private PaymentService paymentService;
@@ -38,43 +34,55 @@ public class PaymentResource {
3834
@Produces(MediaType.APPLICATION_JSON)
3935
@Operation(summary = "Process payment", description = "Process payment using the payment gateway API with fault tolerance")
4036
@APIResponses(value = {
41-
@APIResponse(responseCode = "200", description = "Payment processed successfully"),
42-
@APIResponse(responseCode = "400", description = "Invalid input data"),
43-
@APIResponse(responseCode = "500", description = "Internal server error")
37+
@APIResponse(responseCode = "200", description = "Payment processed successfully",
38+
content = @Content(mediaType = MediaType.APPLICATION_JSON,
39+
schema = @Schema(implementation = String.class))),
40+
@APIResponse(responseCode = "400", description = "Invalid input data",
41+
content = @Content(mediaType = MediaType.APPLICATION_JSON,
42+
schema = @Schema(implementation = String.class))),
43+
@APIResponse(responseCode = "500", description = "Internal server error",
44+
content = @Content(mediaType = MediaType.APPLICATION_JSON,
45+
schema = @Schema(implementation = String.class)))
4446
})
45-
public Response processPayment(@QueryParam("amount") Double amount)
46-
throws PaymentProcessingException, CriticalPaymentException {
47-
48-
// Input validation
47+
public CompletionStage<Response> processPayment(@QueryParam("amount") Double amount) {
48+
4949
if (amount == null || amount <= 0) {
50-
throw new CriticalPaymentException("Invalid payment amount: " + amount);
50+
return CompletableFuture.completedFuture(
51+
Response.status(Response.Status.BAD_REQUEST)
52+
.entity("{\"status\":\"error\", \"message\":\"Invalid payment amount: " + amount + "\"}")
53+
.type(MediaType.APPLICATION_JSON)
54+
.build()
55+
);
5156
}
5257

53-
try {
54-
// Create PaymentDetails using constructor
55-
PaymentDetails paymentDetails = new PaymentDetails(
56-
"****-****-****-1111", // cardNumber - placeholder for demo
57-
"Demo User", // cardHolderName
58-
"12/25", // expiryDate
59-
"***", // securityCode
60-
BigDecimal.valueOf(amount) // amount
61-
);
58+
PaymentDetails paymentDetails = new PaymentDetails(
59+
"****-****-****-1111",
60+
"Demo User",
61+
"12/25",
62+
"***",
63+
BigDecimal.valueOf(amount)
64+
);
6265

63-
// Use PaymentService with full fault tolerance features
64-
CompletionStage<String> result = paymentService.processPayment(paymentDetails);
65-
66-
// Wait for async result (in production, consider different patterns)
67-
String paymentResult = result.toCompletableFuture().get();
68-
69-
return Response.ok(paymentResult, MediaType.APPLICATION_JSON).build();
70-
66+
System.out.println("[" + Thread.currentThread().getName() + "] HTTP request received, handing off to managed executor");
67+
68+
try {
69+
return paymentService.processPayment(paymentDetails)
70+
.thenApply(result -> {
71+
System.out.println("[" + Thread.currentThread().getName() + "] Sending HTTP response for amount: " + amount);
72+
return Response.ok(result, MediaType.APPLICATION_JSON).build();
73+
})
74+
.exceptionally(e -> {
75+
Throwable cause = e.getCause() != null ? e.getCause() : e;
76+
System.out.println("[" + Thread.currentThread().getName() + "] Error for amount " + amount + ": " + cause.getMessage());
77+
return Response.ok(cause.getMessage(), MediaType.APPLICATION_JSON).build();
78+
});
7179
} catch (PaymentProcessingException e) {
72-
// Re-throw to let fault tolerance handle it
73-
throw e;
74-
} catch (Exception e) {
75-
// Handle other exceptions
76-
throw new PaymentProcessingException("Payment processing failed: " + e.getMessage());
80+
return CompletableFuture.completedFuture(
81+
Response.serverError()
82+
.entity("{\"status\":\"error\", \"message\":\"" + e.getMessage() + "\"}")
83+
.type(MediaType.APPLICATION_JSON)
84+
.build()
85+
);
7786
}
7887
}
79-
8088
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package io.microprofile.tutorial.store.payment.service;
2+
3+
import org.eclipse.microprofile.faulttolerance.ExecutionContext;
4+
import org.eclipse.microprofile.faulttolerance.FallbackHandler;
5+
import org.eclipse.microprofile.faulttolerance.exceptions.BulkheadException;
6+
7+
import java.util.concurrent.CompletableFuture;
8+
import java.util.concurrent.CompletionStage;
9+
10+
public class PaymentFallbackHandler implements FallbackHandler<CompletionStage<String>> {
11+
12+
@Override
13+
public CompletionStage<String> handle(ExecutionContext context) {
14+
if (context.getFailure() instanceof BulkheadException) {
15+
System.out.println("Bulkhead rejected request for method: " + context.getMethod().getName());
16+
return CompletableFuture.completedFuture(
17+
"{\"status\":\"rejected\", \"message\":\"Bulkhead full: too many concurrent requests.\"}");
18+
}
19+
System.out.println("Fallback invoked: " + context.getFailure().getMessage());
20+
return CompletableFuture.completedFuture(
21+
"{\"status\":\"failed\", \"message\":\"Payment service is currently unavailable.\"}");
22+
}
23+
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ public class PaymentService {
3737
jitter = 500,
3838
retryOn = PaymentProcessingException.class,
3939
abortOn = CriticalPaymentException.class)
40-
@Fallback(fallbackMethod = "fallbackProcessPayment")
41-
@Bulkhead(value=5)
40+
@Fallback(PaymentFallbackHandler.class)
41+
@Bulkhead(value = 5, waitingTaskQueue = 2)
4242
@CircuitBreaker(
43-
failureRatio = 0.5,
44-
requestVolumeThreshold = 4,
43+
failureRatio = 0.75,
44+
requestVolumeThreshold = 10,
4545
delay = 3000
4646
)
4747
public CompletionStage<String> processPayment(PaymentDetails paymentDetails) throws PaymentProcessingException {

code/chapter08/payment/src/main/liberty/config/server.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<server description="MicroProfile Tutorial Liberty Server">
22
<featureManager>
33
<platform>jakartaEE-10.0</platform>
4-
<platform>microProfile-6.1</platform>
4+
<platform>microProfile-7.1</platform>
55
<feature>restfulWS</feature>
66
<feature>jsonp</feature>
77
<feature>jsonb</feature>

code/chapter08/payment/test-payment-bulkhead.sh

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ echo -e "${CYAN}Base URL: $BASE_URL${NC}"
5151
echo ""
5252

5353
# Set up log monitoring
54-
LOG_FILE="/workspaces/liberty-rest-app/payment/target/liberty/wlp/usr/servers/mpServer/logs/messages.log"
54+
LOG_FILE="/workspaces/microprofile-tutorial/code/chapter08/payment/target/liberty/wlp/usr/servers/mpServer/logs/messages.log"
5555

5656
# Get initial log position for later analysis
5757
if [ -f "$LOG_FILE" ]; then
@@ -71,17 +71,18 @@ echo ""
7171
# ====================================
7272
echo -e "${BLUE}=== PaymentService Bulkhead Configuration ===${NC}"
7373
echo -e "${YELLOW}Your PaymentService has these bulkhead settings:${NC}"
74-
echo -e "${CYAN}• Maximum Concurrent Requests: 5${NC}"
74+
echo -e "${CYAN}• Maximum Concurrent Threads: 5${NC}"
75+
echo -e "${CYAN}• Waiting Task Queue: 2${NC}"
76+
echo -e "${CYAN}• Effective Capacity: 7 (5 running + 2 queued)${NC}"
7577
echo -e "${CYAN}• Asynchronous Processing: Yes${NC}"
7678
echo -e "${CYAN}• Retry on failure: Yes (3 retries)${NC}"
7779
echo -e "${CYAN}• Processing delay: ~1.5 seconds per attempt${NC}"
7880
echo ""
7981

80-
echo -e "${YELLOW}🔍 WHAT TO EXPECT WITH BULKHEAD:${NC}"
81-
echo -e "${CYAN}• Only 5 concurrent requests will be processed at a time${NC}"
82-
echo -e "${CYAN}• Additional requests beyond the limit will be rejected${NC}"
83-
echo -e "${CYAN}• Rejected requests will receive a 'Bulkhead full' message${NC}"
84-
echo -e "${CYAN}• Successfully queued requests complete in ~1.5-10 seconds${NC}"
82+
echo -e "${YELLOW}WHAT TO EXPECT WITH BULKHEAD (10 concurrent requests):${NC}"
83+
echo -e "${CYAN}• 5 requests processed immediately, 2 queued — 7 total accepted${NC}"
84+
echo -e "${CYAN}• 3 requests rejected instantly with 'Bulkhead full' status${NC}"
85+
echo -e "${CYAN}• Accepted requests complete in ~1.5 seconds${NC}"
8586
echo ""
8687

8788
echo -e "${YELLOW}Make sure the Payment Service is running on port 9080${NC}"
@@ -114,10 +115,10 @@ send_request() {
114115
if echo "$response" | grep -q "success"; then
115116
echo -e "${GREEN}[Request $id] SUCCESS: Payment processed in ${duration}s${NC}"
116117
echo -e "${GREEN}[Request $id] Response: $response${NC}"
117-
elif echo "$response" | grep -q "Bulkhead"; then
118+
elif echo "$response" | grep -q "rejected"; then
118119
echo -e "${YELLOW}[Request $id] REJECTED: Bulkhead full (took ${duration}s)${NC}"
119120
echo -e "${YELLOW}[Request $id] Response: $response${NC}"
120-
elif echo "$response" | grep -q "fallback"; then
121+
elif echo "$response" | grep -q "failed"; then
121122
echo -e "${PURPLE}[Request $id] FALLBACK: Service used fallback (took ${duration}s)${NC}"
122123
echo -e "${PURPLE}[Request $id] Response: $response${NC}"
123124
else
@@ -169,7 +170,14 @@ for pid in "${pids[@]}"; do
169170
wait $pid
170171
done
171172

172-
# Collect results
173+
# Display results in order (output was suppressed while running in background)
174+
echo ""
175+
echo -e "${BLUE}=== Concurrent Request Results ===${NC}"
176+
for i in {1..10}; do
177+
cat /tmp/bulkhead_result_$i.txt
178+
done
179+
180+
# Count results by grepping the captured output files
173181
echo ""
174182
echo -e "${BLUE}=== Results Summary ===${NC}"
175183
success_count=0
@@ -178,26 +186,21 @@ fallback_count=0
178186
error_count=0
179187

180188
for i in {1..10}; do
181-
result=$(cat /tmp/bulkhead_result_$i.txt)
182-
rm /tmp/bulkhead_result_$i.txt
183-
results+=($result)
184-
185-
# Count results by parsing the output log
186-
log_entry=$(grep "\[Request $i\]" /tmp/bulkhead.log 2>/dev/null || echo "")
187-
if echo "$log_entry" | grep -q "SUCCESS"; then
189+
if grep -q "SUCCESS" /tmp/bulkhead_result_$i.txt 2>/dev/null; then
188190
success_count=$((success_count + 1))
189-
elif echo "$log_entry" | grep -q "REJECTED"; then
191+
elif grep -q "REJECTED" /tmp/bulkhead_result_$i.txt 2>/dev/null; then
190192
rejected_count=$((rejected_count + 1))
191-
elif echo "$log_entry" | grep -q "FALLBACK"; then
193+
elif grep -q "FALLBACK" /tmp/bulkhead_result_$i.txt 2>/dev/null; then
192194
fallback_count=$((fallback_count + 1))
193195
else
194196
error_count=$((error_count + 1))
195197
fi
198+
rm /tmp/bulkhead_result_$i.txt
196199
done
197200

198201
echo -e "${CYAN}Successful requests: $success_count${NC}"
199-
echo -e "${CYAN}Rejected requests: $rejected_count${NC}"
200-
echo -e "${CYAN}Fallback requests: $fallback_count${NC}"
202+
echo -e "${CYAN}Rejected requests (bulkhead full): $rejected_count${NC}"
203+
echo -e "${CYAN}Fallback requests (retries exhausted): $fallback_count${NC}"
201204
echo -e "${CYAN}Error requests: $error_count${NC}"
202205

203206
# ====================================

0 commit comments

Comments
 (0)