Request-Response
Direct-to-target
Here are the steps for implementing a direct-to-target request/response pattern using a Pub/Sub message broker. This involves creating unique custom subscriptions per actor so that the responder knows where to publish their response.
-
Responding Actor Subscribes
The actor designed to respond (the "responder") subscribes to the Pub/Sub broker for a specific query topic, e.g.,
topic/query/my_thing. This tells the broker to forward all messages published to this topic to the responder actor. -
Requesting Actor Prepares and Subscribes
The actor initiating the request (the "requester") generates a unique reply topic (e.g., based on its own ID and the request ID) and then subscribes to this unique topic with the broker. This ensures the requester will receive messages specifically addressed back to it.
-
Requesting Actor Publishes Request
The requester actor publishes the request message to the responder's query topic (
topic/query/my_thing). The message payload includes the necessary query data and the unique reply topic generated in step 2 (often in a field likereply_to). -
Broker Routes Request
The Pub/Sub broker receives the message published to
topic/query/my_thing. Based on the subscription from step 1, the broker routes this message to the responding actor. -
Responding Actor Processes and Publishes Response
The responder actor receives the request message, processes it, generates a response, and extracts the
reply_totopic from the incoming message. It then publishes the response message to this extractedreply_totopic. The responder does not need to know the identity orActorRefof the requester. -
Broker Routes Response
The Pub/Sub broker receives the message published to the unique
reply_totopic. Based on the subscription from step 2, the broker routes this response message to the requesting actor. -
Requesting Actor Receives Response
The requester actor receives the response message on its unique reply topic.
This pattern effectively decouples the requesting and responding actors, as they only need to know about the Pub/Sub broker and agreed-upon topics, not each other's specific addresses.
sequenceDiagram
participant Requester as Requesting Actor
participant Broker as Pub/Sub Broker
participant Responder as Responding Actor
Note over Requester,Responder: Setup Phase
Responder->>Broker: Subscribe("topic/query/my_thing") (when actor spawns)
Requester->>Broker: Subscribe("unique/reply/topic")
Note over Requester,Responder: Request/Response Phase
activate Requester
Requester->>Broker: Publish("topic/query/my_thing", reply_to="unique/reply/topic")
activate Broker
Broker-->>Responder: Message("topic/query/my_thing", reply_to="unique/reply/topic")
deactivate Broker
activate Responder
Responder->>Broker: Publish("unique/reply/topic", response_data)
deactivate Responder
activate Broker
Broker-->>Requester: Message("unique/reply/topic", response_data)
deactivate Broker
deactivate Requester
Gateway Correlation ID Mapping
The direct request/response pattern using unique, per-request reply_to topics requires each requesting actor to manage its own temporary subscription. This can become cumbersome, especially if the requesting actors are short-lived or if there are a very large number of concurrent requests. An alternative approach leverages a central Gateway Actor to manage the response routing using Correlation IDs.
In this pattern:
-
Gateway Subscription
A dedicated Gateway Actor maintains a single, long-lived subscription to a fixed
reply_totopic (e.g.,gateway/replies). This is the only topic responses are sent to via the broker. -
Responder Subscription
Responding actors subscribe to their specific query topics with the broker, same as before (e.g.,
topic/query/my_thing). -
Request from Internal Actor
A requesting actor (behind the Gateway) sends a request message directly to the Gateway Actor, including the intended query topic and payload.
-
Gateway Action - Publish with Correlation ID
The Gateway Actor receives the internal request. It generates a unique Correlation ID (or reads the one the actor generated) for this request and stores a mapping internally, linking this Correlation ID to the specific requesting actor's
ActorRef. The Gateway then publishes the request message to the broker using the intended query topic (topic/query/my_thing). The crucial difference is that thereply_tofield in the published message is set to the Gateway's own fixed reply topic (gateway/replies), and the message payload includes the generated Correlation ID.- Gateway Map:
CorrelationID -> RequestingActorRef - Published Message: Publish to
topic/query/my_thingwith payload including query data andcorrelation_id,reply_to: gateway/replies.
- Gateway Map:
-
Broker Routes Request
The Pub/Sub broker routes the request message to the subscribing responding actor(s) based on the query topic.
-
Responding Actor Action - Publish Response
The responding actor receives the request. It processes it and prepares a response. It takes the
correlation_idfrom the incoming message and includes it in the response payload. It then publishes the response message to the topic specified in thereply_tofield of the incoming message, which is the Gateway's fixed reply topic (gateway/replies).
- Published Message: Publish to
gateway/replieswith payload including response data and the samecorrelation_id.
-
Broker Routes Response
The Pub/Sub broker routes the response message to the Gateway Actor because the Gateway is subscribed to
gateway/replies. -
Gateway Action - Route to Requester
The Gateway Actor receives the response message. It extracts the
correlation_idfrom the payload. Using its internal mapping, it looks up theActorRefof the original requesting actor associated with that Correlation ID. It then sends the response message directly to the requesting actor'sActorRef. After routing, the Gateway may remove the mapping entry for this Correlation ID (depending on whether duplicate responses are possible).- Action: Receive message on
gateway/replies. Extractcorrelation_id. Look upRequestingActorRefin internal map. Send response message directly toRequestingActorRef.
- Action: Receive message on
sequenceDiagram
participant ReqInternal as Requesting Actor (Internal)
participant Gateway as Gateway Actor
participant Broker as Pub/Sub Broker
participant Responder as Responding Actor (External)
Note over ReqInternal,Responder: Setup Phase
Gateway->>Broker: Subscribe("gateway/replies")
Responder->>Broker: Subscribe("topic/query/my_thing")
Note over ReqInternal,Responder: Request/Response Phase
activate ReqInternal
ReqInternal->>Gateway: Request(query_data)
deactivate ReqInternal
activate Gateway
Gateway->>Gateway: Generate/Store Mapping (CorrID -> ReqInternal)
Gateway->>Broker: Publish("topic/query/my_thing", correlation_id, reply_to="gateway/replies")
deactivate Gateway
activate Broker
Broker-->>Responder: Message("topic/query/my_thing", correlation_id, reply_to="gateway/replies")
deactivate Broker
activate Responder
Responder->>Broker: Publish("gateway/replies", correlation_id, response_data)
deactivate Responder
activate Broker
Broker-->>Gateway: Message("gateway/replies", correlation_id, response_data)
deactivate Broker
activate Gateway
Gateway->>Gateway: Lookup Mapping (CorrID -> ReqInternal)
Gateway->>ReqInternal: Response(response_data)
deactivate Gateway
Benefits
- Simplifies Requesting Actors: Requesting actors no longer need to manage individual subscriptions or unique reply topics. They simply send their request to the well-known Gateway.
- Better for Short-Lived Actors: This pattern is ideal when requesting actors are transient, as the long-lived Gateway handles the durable subscription needed to receive the response.
- Centralized Management: The complexity of correlating requests and responses is centralized in the Gateway.
Considerations
- State Management in Gateway: The Gateway must reliably store and manage the Correlation ID to
ActorRefmapping. This requires careful consideration of potential Gateway restarts, memory usage for long-running transactions, and handling potential timeouts or orphaned mappings if a requesting actor dies before receiving a response. - Single Point of Failure: If the Gateway actor fails, all in-flight requests awaiting responses will be lost unless the Gateway's state is persisted and recoverable.
This pattern is particularly useful when you have a boundary (like a Bounded Context boundary managed by the Gateway/Router actor) and many internal actors that need to initiate requests to external services or actors via a Pub/Sub bus without exposing their individual presence or managing complex per-request subscriptions.