REST facade over a slow SOAP backend with duplicate POSTs
An insurer exposes a REST API built as an ACE REST API project. Partners call POST /v1/claims and GET /v1/claims/{claimId}. The POST flow is HTTPInput, a Compute node that validates JSON, SOAPRequest to a claims backend, a mapping Compute node, and HTTPReply. The backend SOAP service has p95 latency of 2.5 seconds and occasionally returns a SOAP Fault with HTTP 500. The partner contract requires p95 under 800 ms and 2,000 requests/min. On backend timeout, the current flow returns HTTP 500 with a Java stack trace and internal host name. Partners retry POSTs automatically, and the claims team has found duplicate claims. The partner has also asked for an Idempotency-Key header and a stable error format. GET traffic is read-only and can tolerate a short cache.
1. The backend p95 is 2.5 seconds but the API SLA is 800 ms. What API design and flow changes would you make?
Read the worked answer
A synchronous REST call cannot beat a 2.5-second backend p95 by tuning ACE alone. For POST, change the contract to asynchronous acceptance: validate the request, persist a claim request with a generated claimId, put the work on a queue, and return HTTP 202 with a Location header pointing to /v1/claims/{claimId}/status. A separate flow calls the SOAP backend at its own pace and records success or failure. The partner polls the status resource. This meets the 800 ms SLA because the API no longer waits for the backend.
If the business insists on synchronous POST, the p95 SLA is not achievable with this backend unless the backend is changed or a cache is added for duplicate requests. RESTAsyncRequest plus an aggregate can free the HTTP thread while waiting, but it does not reduce the 2.5-second latency seen by the caller; it only improves ACE capacity. Do not confuse thread efficiency with response-time SLA.
For GET, cache claim status for a short, explicit TTL, for example 30 to 60 seconds, and document staleness. Invalidate the cache when the status changes. Ensure the REST API OpenAPI definition reflects 202, the status resource, and the error schema.
2. Design the error handling and response contract. How should SOAP faults, backend timeouts, and validation errors map to HTTP status and payloads?
Read the worked answer
Separate caller errors from backend failures. Invalid JSON or missing required fields should return 400 with a problem+json body listing field paths; do not call the backend. A downstream SOAP Fault that indicates a business rule rejection should map to 422 or 409, depending on semantics, with a stable error code. A backend timeout or connection failure should map to 504 Gateway Timeout or 502 Bad Gateway, not 500, and must not expose stack traces, internal host names, or SOAP fault details.
Implement a single error handler that catches SOAP faults and transport exceptions, logs a correlation ID with the original fault at ERROR level, and returns a sanitized JSON body with type, title, status, code, and correlationId. Set explicit connect and read timeouts on SOAPRequest; do not rely on defaults. Distinguish retryable from non-retryable errors so the asynchronous worker can decide whether to retry or mark the claim failed.
For the asynchronous POST, the initial 202 response should still report validation errors synchronously. Backend errors are reported through the status resource, not as a delayed HTTP response. Keep the error codes in the OpenAPI schema so partners can code against them.
3. How would you implement Idempotency-Key handling so partner retries do not create duplicate claims?
Read the worked answer
Require the Idempotency-Key header on POST and store a record keyed by partner identity plus the header value. The record contains a hash of the request body, the generated claimId, processing state (in-progress, completed, failed), and the response payload with a TTL, for example 24 hours. On the first request, perform an atomic create-if-absent. If it succeeds, process and later update the record with the result. If it fails because the key exists, compare the request hash: if it differs, return 422 to signal key reuse with a different payload; if it matches, return the stored response or the same claimId with 202 while processing is in progress.
Concurrency matters. Two simultaneous retries can both see no record. Use a database unique constraint or a cache putIfAbsent with a short in-progress lock. The loser waits briefly or returns 409 with Retry-After, then re-reads the record. Do not rely on the downstream SOAP service to de-duplicate unless it exposes an idempotency key and honors it.
The storage must survive integration server restart; LocalEnvironment or a shared variable is not sufficient. Clean up expired keys, and make the idempotency lookup part of the API contract documentation.