Request-Reply

Table of Contents

This page defines the service architecture pattern Request-Reply in the pattern language. A requester sends a message and receives one reply on an address the request names. In ORE Studio every service operation is a NATS request with a private inbox as its reply address.

1. Also known as

  • Request-response.
  • Return Address, the companion pattern in Hohpe and Woolf for the reply's destination.

2. Context

Services talk over a message bus, not direct connections. A caller needs an answer: data, a decision, or a refusal.

3. Problem

How does a service ask another for an answer over a bus that only moves one-way messages?

4. Forces

  • The bus delivers messages one way; a reply needs an address.
  • The caller must not wait forever for an answer that may never come.
  • A refusal must be told apart from an answer, or a client decodes an error as data.
  • Several replicas serve one subject, and only one should answer.

5. Solution

The requester creates a private inbox and sends the request with the inbox as its reply address. One replica of the serving queue group receives it, acts, and publishes one reply to the inbox. A refusal is a reply too: an empty body with an X-Error header. The requester waits up to its timeout, reads X-Error first, and only then decodes the body.

pattern_request_reply.png

Figure 1: Request-Reply: the request names its inbox, one replica answers, and a refusal is an empty reply with X-Error.

Source: [[./pattern_request_reply.puml][pattern_request_reply.puml]]. Regenerate with plantuml -tpng doc/knowledge/service_architecture_patterns/pattern_request_reply.puml.

6. Failure handling

Concern What the pattern does
Errors A refusal is an X-Error reply: unauthorized, forbidden, bad_request or token_expired. A client reads the header before the body.
Timeouts The requester waits at most its timeout; see Timeout. No reply does not prove the work was not done.
Retries Only token_expired is retried, once, after a renewal; see Retry.
Idempotency A repeated request is a new delivery; the replier must tolerate it if the operation writes.
Observability The subject, the correlation id and the outcome are logged on both sides.

7. Pitfalls

  • A refusal decoded as a body. The client reports a decode error; the real cause is the X-Error header it did not read.
  • No subscriber. A request to a subject nobody serves waits for its whole timeout and then fails, with nothing in any service's log.
  • A long operation behind a request. Work that takes minutes holds the caller; it belongs in a workflow that answers at once with a run id.
  • A timeout read as "nothing happened". The replier may have finished after the caller gave up.

8. Security

The request carries the caller's token, and the replier authorises it on every request. The inbox is private to the requester's connection, so another client cannot read the reply.

9. Resulting context

Benefits
A service call over the bus, with one answer and a clear refusal path.
Liabilities
The caller is blocked for the call's length, and a slow replier holds it.

10. In ORE Studio

The curve construction, Bootstrapping a curve: from quotes to a published set, is a request-reply: the compute reads the quotes and answers the solved points.

The surface construction, Constructing a volatility surface: from quotes to a published set, is one too: the calibration reads the quotes and answers the calibrated points and their errors.

The market data path, a public fixing, a curve and a report, is a chain of it: each hop calls a service and waits for the answer before the next.

The default timeout is 30 seconds, set once in ores.nats/service/timeouts.hpp for every overload.

11. Related patterns

12. NATS patterns

The NATS service patterns hub states how each of these is written in ores.nats code:

13. Sources

14. See also

Emacs 29.3 (Org mode 9.6.15)