docs
Systems
Messaging
RequestSystem
Architecture and Design

Design patterns in use

  • Message inheritance. Request derives from Message and implements IRequest. The base class supplies the target domain and the params bag.
  • Routing by key. RequestBus extends MessageBus<IRequest, Domain> and returns each request's TargetDomain as its routing key. The base class stores one handler per domain and queues messages for domains that are not ready.
  • Callback-based reply. The sender supplies an Action<Response> when creating the Request. The dispatcher invokes it when the reply arrives.
  • Correlation by ID. RequestTracker assigns each request an integer ID and stores it in a dictionary. Each Response carries the ID of the request it answers, and the tracker uses it to find that request.
  • Interface contracts. IRequest describes what a request exposes to receivers. IRequestHandler describes a handler with a HandleRequest method.

Connections to other systems

  • Contracts. The Domain enum identifies the system that owns a request. Only the enum is relevant here.
  • Params. Request inherits Params from Message. IRequest exposes it so handlers can read extra data stored as typed params.
  • Message. Request is a Message, and its Payload is typed as Message, usually a command.
  • BusRegistry. Holds the RequestDispatcher instance. Other systems retrieve it through ServiceLocator.
  • BusUtility. Send(Request) forwards to RequestDispatcher.Ask. SendMessage(Message) also routes Request instances to that overload.
  • MessageBus. Supplies routing, handler storage, and the queue that holds requests while a domain has no handler.
  • SignalBus. Used indirectly by MessageBus for ready signals. This system never sets a ready signal on its bus, so requests are delivered as soon as a handler is registered for the domain.

Key concepts and terminology

  • Request. A message asking the system that owns a domain to act on the sender's behalf.
  • Response. The reply from the receiving system. It holds the request ID, a RequestStatus, and a reason when rejected.
  • Target domain. The Domain value a request is routed by.
  • Verb. An integer identifying what the request asks for. The source documents it as a domain-specific value, but the enum that defines it is not part of the source.
  • Payload. The Message the receiving system acts on if it accepts, usually a command.
  • Request ID. An integer assigned when the request is sent. It starts at 0 and increases for the session.
  • Pending request. A request that has been sent and tracked but not yet answered.
  • Handler. An Action<IRequest> registered per domain with Register.

Event and data flow

  1. The sender creates a Request with a target domain, a verb, an optional payload, and an optional response callback. It can also set values in Params.
  2. The sender calls BusUtility.Send(request), or RequestDispatcher.Ask(request) directly.
  3. Ask calls RequestTracker.Track. The tracker assigns the next ID, sets it on the request, and stores the request in its pending dictionary.
  4. Ask passes the request to RequestBus.Send, which routes it by TargetDomain.
  5. If a handler is registered for the domain, the handler receives the request immediately. If no handler exists, MessageBus logs a warning and queues the request. Queued requests are delivered in order when a handler is registered.
  6. The handler decides whether to accept. It calls RequestDispatcher.Reply with Response.Accepted(request) or Response.Rejected(request, reason).
  7. Reply calls RequestTracker.TryResolve, which removes the pending request with the matching ID. If found, the dispatcher invokes that request's callback with the response. If not found, for example for a duplicate or late reply, it logs a warning.