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 handlingand* Pitfallssay 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:
- Specialising. The outer pattern exists and NATS makes it concrete: Competing Consumers becomes Queue-Group Subscription; Timeout becomes Bounded Request. The page cites the outer pattern's source and the NATS document.
- Bus-specific. No outer pattern covers it, because the question is a property of NATS: Acknowledge What Was Handled answers a question only a durable consumer has. The page cites the NATS document alone.
- Substrate. The page names a NATS concept the groups below rest on: Prefix Every Subject With make_subject. Cited to the NATS documentation, with no outer pattern.
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
- ores.nats — the library every pattern here is stated in terms of.
- NATS — the substrate: subjects, pub/sub, request/reply, queue groups, JetStream.
- JetStream streams and durable consumers — the streams this deployment creates and what a subject rename strands.
- Service Bootstrap Phases — when a subject becomes callable, and why that order matters.
- Knowledge — the index this structure note hangs from.
- Zettelkasten — what a structure note is, and why its order is authored rather than computed.
- NATS documentation, Delivery and acknowledgment — the ack/redeliver loop the subscription group rests on.
- NATS documentation, Request-Reply Resilience — the two request failures and how to retry them.
- NATS documentation, Drain & Shutdown — close against drain, and why drain belongs last.