docs
Systems
Messaging
QuerySystem
Architecture and Design

Design patterns

  • Typed key pattern. Query inputs and outputs are stored in Params under ParamKey<T> instances. Each key declares its value type, so reads return the correct type without casting. Keys are defined as static readonly fields in QueryKeys, with names built from constants in QueryKeyNames.
  • Static facade. QueryFactory exposes static methods such as GetCurrentLevel(). Each method builds a query, sends it, and returns the typed value, so callers never handle the message directly.
  • Read-only message. A query is a message whose handler answers it by filling in Result and Status. The IQuery documentation states that handlers must not change state while answering. This rule is a documented convention and is not enforced by any code in these blocks.
  • Routing by key with queuing. The query bus routes each query by TargetDomain to a single registered handler. If no handler exists, or the domain is not ready, the query is queued. The routing and queuing logic lives in the message bus base class (dependency).

Connections to dependencies

  • Contracts (Domain). Query.TargetDomain is a Domain value. Each factory method picks the domain that owns the data, such as Domain.Level for time in level and Domain.Game for current level and game speed.
  • Params. Query.Params and Query.Result are Params instances. Keys from QueryKeys are passed to Set, Get, and Has.
  • Bus (QueryBus, BusUtility, BusRegistry). BusUtility.AskAndGet sends the query through the query bus and reads the result key from the answered query. The query bus is reached through the service locator, which holds the bus registry. Owning systems register their handlers on the query bus for their domain.
  • Bus (MessageBus). Queries that arrive before a handler exists, or before the domain signals ready, are queued in FIFO order and released when the handler registers or the ready signal is raised.

Key concepts and terminology

  • Domain. The system that owns a category of data. Queries are addressed to a domain, not to a specific class.
  • Verb. The kind of question: Get, Own, or Has. The handling system decides what each verb means for its domain. Only Get is used by QueryFactory.
  • Subject. A value in QuerySubjects stored under QueryKeys.QuerySubject in the input params. It tells the handler which data to return within its domain.
  • Params (input). Data the caller sets before sending, such as the subject.
  • Params (result). Data the handler writes when answering. The caller reads it after sending.
  • Status. Pending until answered. The handler may set Success or Failure. The status documentation describes yes-or-no answers, but no query in these blocks uses Own or Has.
  • Ready signal. An optional signal that gates a domain's queue, set through the message bus base class (dependency).

Event and data flow

  1. A caller invokes a QueryFactory method, such as GetTimeInLevel().
  2. The factory creates a Query with the target domain and QueryVerb.Get.
  3. The factory sets QueryKeys.QuerySubject in the input params to the matching QuerySubjects value.
  4. The factory calls BusUtility.AskAndGet, which sends the query to the query bus and gets back the answered query.
  5. The query bus routes the query by TargetDomain.
  6. If a handler exists and is ready, the handler fills in the result params and status. The answered query is returned to the caller.
  7. If no handler exists, or the domain is not ready, the query is queued.
  8. When the handler registers, or the ready signal is raised, queued queries are routed in FIFO order.
  9. AskAndGet reads the value under the result key and returns it as the key's type.

Thread safety

Not addressed in the code for this system.