Loading…
Loading…
Manages the persistent state of asynchronous API executions, including creation, status tracking, polling security, and finalization via callbacks.
The Asynchronous Execution Repository is a data access component responsible for managing the lifecycle and persistent state of asynchronous API operations. When an API consumer initiates an operation that is processed asynchronously (returning a 202 Accepted response), this repository creates and manages a record of that execution.
This component solves the problem of tracking long-running, out-of-band operations. It provides the persistence layer for the following capabilities:
waiting_callback to completed, failed, or expired.All database operations performed by this repository are strictly scoped to the tenant of the authenticated caller, preventing any possibility of one tenant accessing or modifying another tenant's execution records.
The repository manages an s2r_async_execution database table where each row represents a single asynchronous operation. The lifecycle of an execution is managed through a series of state transitions orchestrated by this component's methods.
sequenceDiagram
participant C as API Consumer
participant P as API Platform
participant R as Async Execution<br>Repository
participant D as Database
C->>P: 1. Initiate async API call
P->>R: 2. createPending(...)
R->>D: 3. INSERT into s2r_async_execution<br>(status: 'waiting_callback')
R-->>P: Returns new AsyncExecutionRecord
P-->>C: 4. Respond 202 Accepted<br>with correlation ID & poll token
loop Poll for result
C->>P: 5. GET /status?correlationId=...<br>(with poll token)
P->>R: 6. findByCorrelationForCaller(...)
R->>D: 7. SELECT with security checks<br>(tenant, key hash, token hash)
D-->>R: Returns matching record
R-->>P: Returns AsyncExecutionRecord
P-->>C: 8. Respond with current status
end
Note over P, D: Later...
participant B as Backend System
B->>P: 9. Send callback with final result
P->>R: 10. complete(...) or fail(...)
R->>D: 11. UPDATE s2r_async_execution<br>where status = 'waiting_callback'
D-->>R: Returns 1 if row updated
R-->>P: Returns updated record or null
Note over C, P: Later, on next poll...
C->>P: 12. Polls again
P->>P: (Steps 6-8 repeat)
P-->>C: 13. Respond with final status<br>(e.g., 'completed') and payloadWhen an asynchronous operation is initiated, the platform calls the createPending method. This method:
id) for the execution record.INSERT statement for the s2r_async_execution table.correlationId.status to waiting_callback.initiator_tenant_id and a SHA-256 hash of their API key (initiator_key_hash).poll_token that was generated for this execution. The raw token is never stored.expires_at timestamp based on the configured retentionDays.An API consumer polls for the result using the correlationId returned in the initial 202 response. The platform uses the findByCorrelationForCaller method to retrieve the status. This lookup is designed for security:
correlationId, the route identifiers (environment, service_key, operation_key), and the callerTenantId.poll_token. The repository performs a constant-time comparison between the SHA-256 hash of the presented token and the stored poll_token_hash.callerTenantId and callerKeyHash match the initiator_tenant_id and initiator_key_hash stored when the record was created.A record is returned only if all these conditions are met. A mismatch in any condition, an unknown ID, or a missing token are indistinguishable and all result in null being returned, preventing information leakage.
When the backend system completes the work, it sends a callback to the platform, which then calls either complete or fail on the repository.
complete: Updates the record's status to completed and stores the final response payloads from the callback.fail: Updates the status to failed and stores a failure_message.Both update operations are atomic and idempotent. They include where status = 'waiting_callback' in the UPDATE statement. This ensures that a given execution can only be finalized once. Any subsequent or racing callbacks for the same execution will find the status is no longer waiting_callback and the update will affect zero rows.
A background process can call expireIfOverdue to manage records that never receive a callback. This method will update an execution's status from waiting_callback to expired if its expires_at timestamp is in the past.
The following fields are stored for each asynchronous execution in the s2r_async_execution table.
| Field | Type | Description |
|---|---|---|
id | UUID | A unique, system-generated identifier for the database row. |
tenant_id | UUID | The tenant that owns the API service definition. |
correlation_id | UUID | A unique, client-provided identifier for the end-to-end transaction. |
initiator_tenant_id | UUID | The tenant ID of the API consumer that initiated the execution. |
initiator_key_hash | String | A SHA-256 hex hash of the API key used to initiate the execution. Null if no key was used. |
poll_token_hash | String | A SHA-256 hex hash of the secure token required for polling. |
service_id | UUID | The ID of the service that was invoked. |
service_version_id | UUID | The ID of the specific service version that was invoked. |
operation_id | UUID | The ID of the operation that was invoked. |
environment | String | The environment key for the invoked route. |
service_key | String | The service key for the invoked route. |
operation_key | String | The operation key for the invoked route. |
status | String | The current state of the execution. One of waiting_callback, completed, failed, expired. |
request_payload | String | The original JSON request payload sent by the consumer. |
soap_request_payload | String | The transformed SOAP request payload sent to the backend. |
ack_payload | String | The acknowledgment payload returned in the initial 202 Accepted response. |
callback_soap_payload | String | The raw SOAP payload received from the backend callback. |
callback_response_payload | JSONB | The final JSON response payload from the callback, stored if the execution completed. |
failure_message | String | A description of the error if the execution failed or expired. |
callback_message_id | String | An identifier from the callback message, if provided. |
callback_relates_to | String | A correlation identifier from the callback message, if provided. |
created_at | OffsetDateTime | The timestamp when the execution record was created. |
callback_received_at | OffsetDateTime | The timestamp when the backend callback was received. |
completed_at | OffsetDateTime | The timestamp when the execution transitioned to a terminal state (completed, failed, expired). |
expires_at | OffsetDateTime | The timestamp after which the execution is considered expired if no callback is received. |
The component exposes Java methods for internal platform use. The primary methods and their parameters are detailed below.
createPendingCreates a new record for an asynchronous execution in the waiting_callback state.
IMPORTANT
Before calling createPending, the platform must first call correlationIdTaken to ensure the client-provided correlationId is not already in use by any tenant. This check prevents a database constraint violation after the backend operation has already been performed.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
correlationId | UUID | Yes | The client-provided correlation ID. Must be globally unique. |
initiatorTenantId | UUID | Yes | The tenant ID of the API consumer making the request. |
initiatorKeyHash | String | No | SHA-256 hash of the consumer's API key. null if not authenticated with a key. |
pollTokenHash | String | Yes | SHA-256 hash of the secure polling token. |
serviceId | UUID | Yes | The internal ID of the service being executed. |
serviceVersionId | UUID | Yes | The internal ID of the service version being executed. |
operationId | UUID | Yes | The internal ID of the operation being executed. |
environment | String | Yes | The environment key of the route. |
serviceKey | String | Yes | The service key of the route. |
operationKey | String | Yes | The operation key of the route. |
requestPayload | String | No | The original request body from the API consumer. |
soapRequestPayload | String | No | The transformed SOAP request sent to the backend. |
ackPayload | String | No | The response body sent back to the consumer in the initial 202 response. |
retentionDays | int | Yes | The number of days the record should be retained before it can be marked as expired. Minimum is 1. |
Returns
The newly created AsyncExecutionRecord, or null if the record could not be found immediately after creation.
findByCorrelationForCallerFinds an execution record for a polling API consumer, performing strict security checks.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
environment | String | Yes | The environment key of the route. |
serviceKey | String | Yes | The service key of the route. |
operationKey | String | Yes | The operation key of the route. |
correlationId | UUID | Yes | The correlation ID of the execution. |
callerTenantId | UUID | Yes | The tenant ID of the polling API consumer. |
callerKeyHash | String | No | SHA-256 hash of the consumer's API key. |
presentedPollToken | String | Yes | The raw polling token presented by the consumer. |
Returns
The AsyncExecutionRecord if all identifiers and security credentials match; otherwise, null.
completeMarks an execution as completed. This is an atomic, idempotent operation.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | UUID | Yes | The internal ID of the execution record to update. |
callbackSoapPayload | String | No | The raw SOAP payload from the backend callback. |
callbackResponsePayload | Map | No | The structured JSON response payload from the backend. |
callbackMessageId | String | No | An optional message identifier from the callback. |
callbackRelatesTo | String | No | An optional correlation identifier from the callback. |
Returns
The updated AsyncExecutionRecord if the update was successful, or null if the record was not found or was not in the waiting_callback state.
failMarks an execution as failed. This is an atomic, idempotent operation.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | UUID | Yes | The internal ID of the execution record to update. |
callbackSoapPayload | String | No | The raw SOAP payload from the backend callback. |
failureMessage | String | No | A message describing the reason for failure. |
callbackMessageId | String | No | An optional message identifier from the callback. |
callbackRelatesTo | String | No | An optional correlation identifier from the callback. |
A call to findByCorrelationForCaller returns null under several conditions, which are deliberately made indistinguishable to the polling client for security reasons:
correlationId does not exist.presentedPollToken is missing, malformed, or does not match the stored token hash for the execution.callerTenantId does not match the initiator_tenant_id stored for the execution.callerKeyHash does not match the initiator_key_hash stored for the execution.poll_token_hash or initiator_tenant_id is null).A call to complete or fail returns null, indicating that zero rows were updated in the database. This occurs if the targeted execution record was not in the waiting_callback state at the time of the update. This is expected behavior for:
expired state by a cleanup process.This mechanism ensures that the final state of an execution is written exactly once.