NATS service patterns

Table of Contents

1. Summary

Every ORE Studio service is a set of subjects it answers and messages it sends. NATS is the substrate and ores.nats is the wrapper, but neither says how a service must use them. This hub is the answer: the patterns that govern writing NATS code in this codebase, what each one requires, why it is required, and what breaks without it.

A pattern here is a rule about a call site, not a general architectural idea. It states the shape of the code — which `ores.nats` call, with which arguments, in which place — and the guarantee that shape buys. The rule is a contract: a service that does not follow it is wrong, even when it compiles and appears to work, because the failure it invites is silent and arrives later.

Three rules govern the language:

  • One call site, one pattern. A pattern answers one question about one interface, and its page names the exact call that must appear.
  • Every pattern states its rationale and its failure. A rule with no named failure is a style preference, and does not belong here. Each page's * Failure handling and * Pitfalls say what goes wrong without it.
  • The pattern owns the mechanism; the architecture page owns the design. A pattern page states the call and the guarantee. The page that designs a service links the pattern and states only its own choices.

2. Scope

The hub covers how a service speaks on the bus. It does not cover what it says: the subject grammar, the verbs, the envelopes and the error model on the wire belong to the NATS entity protocol specification, and the encoded payload belongs to the NATS Wire Format; both are ordered by Message Queue, not here. It does not cover who a caller is or how a token travels; that is Identity and Access.

2.1. The division of responsibility with the service architecture patterns

The two pattern languages answer different questions about the same bus. A reader must land on the right one, so the boundary is stated here and in the other hub.

  Service architecture patterns This hub
The question Which mechanism should this interaction use, and why? How must this code use the mechanism?
The unit An interaction between two services A call site in `ores.nats`
Grounded in microservices.io, Enterprise Integration Patterns, Azure Architecture Center, the RFCs the NATS documentation, and the `ores.nats` API
Scope Platform-wide, transport-independent in principle NATS and JetStream specifically
Lives in doc/knowledge/service_architecture_patterns/, type service_architecture_pattern doc/knowledge/nats_patterns/, type nats_pattern
Stated as Forces, competing solutions, consequences The exact call, its arguments, and the failure without it

Read the outer page first. The design decision — this interaction is a Request-Reply, that work is Competing Consumers, this failure wants a Retry — is taken in the service architecture language and argued from its sources. Once the decision is made, a page here says how that decision is written in NATS code.

The relationship is one of specialisation, and a page states which kind it is:

Why these are not service architecture patterns: that type requires an authoritative external source and a group from its closed seven-value vocabulary, and its pattern-uses check requires a link from an architecture page for every use. A rule about a call site cannot meet that contract, and forcing one into a group such as resilience would say something untrue. The two types therefore stand apart and reference each other.

3. Reading order

Read the groups in order. A later group assumes the guarantees the earlier groups establish: a publish is only as durable as the subscription that reads it, and an error is only reportable once the message carrying it is acknowledged.

3.1. The addressing group

How does a subject mean the same thing in every environment? A subject is environment-relative in code and fully-qualified on the wire, and the translation happens in exactly one place. Read this group first, because every subject in the groups below is written in its terms.

Pattern Source
Prefix Every Subject With make_subject NATS documentation, Subject-Based Messaging
Name a Subject With a Codegen Constant ORE Studio: ores.codegen protocol facets

3.2. The subscription group

How does a service receive a message exactly once per intended reader? Every subscription is a queue group so replicas share the load. A message that must survive the service being down is read from a durable consumer, and a durable consumer acknowledges what it handled. A durable consumer is safe to rely on only when the three things around it hold. Read this group before writing any handler.

Pattern Source
Queue-Group Subscription NATS documentation, Queue Groups; Hohpe and Woolf
Durable Subscription NATS documentation, JetStream Consumers
Stream Provisioning at Start NATS documentation, JetStream Streams
Acknowledge What Was Handled NATS documentation, Ack responses and redelivery
At Least Once Prerequisites NATS documentation, Retention policies; Hohpe and Woolf

3.3. The publication group

How does a message that must not be lost leave the process? A domain event that outlives the publisher commits through the stream; a message whose current value is all that matters does not. Read this group before publishing anything.

Pattern Source
Commit Through the Stream NATS documentation, JetStream Publishing
Idempotent Message Identity Hohpe and Woolf; NATS documentation, Nats-Msg-Id

3.4. The request group

How does a caller that asks a question behave when the answer does not come? A request is bounded in time and its two failures are distinguishable. Read this group when writing a caller rather than a handler.

Pattern Source
Bounded Request Nygard, Release It!; NATS documentation, Request-Reply
Classify Transport Failure NATS documentation, Request-Reply Resilience

3.5. The error-handling group

What happens when the work fails? A failure is reported, never swallowed, and never acknowledged as done. Read this group before writing a handler, because it decides what the handler must return.

Pattern Source
Report a Refusal Off the Body ORE Studio: ores.service error contract
Report a Failure in the Body ORE Studio: canonical response envelope
Never Let an Exception Cross the Bus ORE Studio: the cnats callback boundary

3.6. The diagnostics group

How does a reader of one service's log establish what happened? Every handled message announces itself once, the trace key travels with the work, and every connection state change is recorded. Read this group last, because it observes the guarantees the groups above establish.

Pattern Source
Announce Every Handled Message microservices.io, Log Aggregation
Propagate the Correlation Id Hohpe and Woolf; W3C Trace Context
Observe the Connection State NATS documentation, Connection Events

3.7. The ownership group

Who owns the work, and how does it end? A subscription handle is a live registration, so something must own it and something must drain it. A unit of work that may be delivered twice is owned for as long as its lease runs, so a second delivery leaves it alone. Read this group when the question is ownership of a registration or of a piece of work.

Pattern Source
Own the Subscription Handle NATS documentation, Drain & Shutdown
Bind Before You Call Your Own Service microservices.io, Self-Contained Service
Claim Before You Work Hohpe and Woolf; NATS documentation, Ack responses and redelivery

4. Choosing a pattern

Start from the question, not the call. Find the group whose question your code faces, then read its pages. Each page's * Context says when the pattern applies and * Related patterns names the alternatives. When no pattern fits, write the code without one and record the gap on the story. Do not stretch a pattern to fit.

To add a pattern, run compass add nats_pattern --pattern-group <group>, fill in every section, name the ores.nats call it governs, and add it to its group above. See How do I create a new doc?.

5. What this cluster does not cover

Message Queue owns the structure of the messaging cluster: the bus, the namespace, the message contract and the binding. This hub is the rulebook for the binding half of it.

Service architecture patterns owns the vocabulary two services share: Request-Reply, Competing Consumers, Retry, Idempotent Receiver, Log Aggregation. Each page here names the pattern there it specialises and states only the mechanism this codebase requires. The division of responsibility between the two hubs is stated under * Scope above.

Anatomy of a Service owns how a service is assembled: its components, its registry entry, its certificates, its accounts. This hub owns how the assembled service speaks.

6. See also

Emacs 29.3 (Org mode 9.6.15)