Lesson 3 · Core NATS & the JetStream model

JetStream & streams

The durable store behind the subject — what a stream is, how it decides when to delete a message, and who creates it in this repo.

Your win: explain what a JetStream stream is, read a real stream config, and say why this repo chose interest retention — and who provisions streams.

A stream is a durable store for subjects

Turn on JetStream and a stream can capture the messages published to a set of subjects into a durable, ordered log. Now a message survives even if no consumer is listening yet — it waits in the stream until a consumer reads and acknowledges it. That's the whole leap from at-most-once to at-least-once.1 A stream has four things worth knowing: its subjects, a retention policy, a storage type, and a replica count.

Retention: when a message is deleted

Streams don't keep messages forever — the retention policy decides when they go:2

PolicyDeletes a message when…
Limits (default)a limit is hit — max age, max bytes, or max messages
WorkQueuea consumer acks it (a strict FIFO queue; one consumer)
Interestevery interested consumer has received it (kept only while someone still needs it)
Why this repo uses Interest almost everywhere Its events are fan-out to a known set of consumers — usermgmt publishes Staff.Upserted; conversationmgmt and others consume it. Interest retention keeps each message exactly as long as a registered consumer hasn't taken it, then drops it — no unbounded log, no manual cleanup, but still durable through a subscriber restart. It's the natural fit for "deliver this event to the services that care, then forget it," which is most of the repo's NATS traffic.

A real stream config

cmd/server/fink/streams/notificationmgmt.go:9-21
&nats.StreamConfig{
    Name:      constants.StreamNotification,   // "notification"
    Retention: nats.InterestPolicy,            // keep only while a consumer still needs it
    Subjects:  []string{"Notification.*"},   // captures all Notification.* events
    Replicas:  3,                              // three copies for fault tolerance (RAFT)
    MaxBytes:  4 GB,                           // a size limit still applies on top
}

Who creates streams: the fink job

Streams aren't created by the services that use them — they're provisioned centrally by the fink job, which collects every service's stream configs and upserts them:

cmd/server/fink/upsert_streams_nats.go + internal/golibs/nats/jetstream.go:360-403
upsertStreams: for every declared stream config → jsm.UpsertStream(cfg)

// UpsertStream is a hand-rolled idempotent upsert (vanilla NATS has no such helper):
if isLocal { cfg.Replicas = 1 }
if stream exists && retention differs → DELETE + re-ADD     // retention can't be changed in place
else if subjects/replicas/maxAge/maxBytes differ → UpdateStream
else if not found → AddStream
Two provisioning boundaries to keep straight Streams are provisioned by fink (a deploy-time job). Consumers (Lesson 4) are provisioned at subscribe time by the golib, when a service registers its subscriber. So a stream is shared infrastructure; a consumer belongs to a subscriber. (Note: conversationmgmt owns no streams — it's subscriber-only, reading streams that usermgmt and others provisioned.)
Read this next

JetStream streams

Retention policies, storage, replicas, and limits — the config you just read, in depth.

docs.nats.io — Streams
JetStream model deep dive (retention & limits interplay)

Check yourself (from memory)

Q1. What does a JetStream stream add over core NATS pub/sub?

The stream persists messages, so a subscriber that's down doesn't lose them — the leap to at-least-once.

Q2. Why does this repo use InterestPolicy retention?

Fan-out to known consumers: interest keeps a message durable until its consumers take it, then removes it — no unbounded log, no cleanup.

Q3. Who provisions streams, and when?

fink collects every service's stream configs and upserts them. Consumers, by contrast, are created at subscribe time.
Recall: JetStream streams — retention, storage, replicas, provisioning.
what + 3 retentions + config + who, then reveal
A stream = a durable, ordered store capturing messages on a set of subjects → survives with no live consumer (at-least-once). Retention (when to delete): Limits (age/bytes/msgs) · WorkQueue (on ack, FIFO) · Interest (while a consumer still needs it) — repo uses Interest (fan-out to known consumers, then drop). Storage = file (durable) here; Replicas = 3 (RAFT; isLocal→1); limits (MaxBytes) apply on top. Provisioning: the fink job upserts streams at deploy time (UpsertStream = idempotent, delete+recreate on retention drift). Consumers are provisioned separately at subscribe time. conversationmgmt owns no streams (subscriber-only).
🎯 Interview one-liner "How are your JetStream streams configured?" → "Interest retention almost everywhere — messages persist only while a registered consumer still needs them, which fits fan-out of domain events. File storage, three replicas via RAFT for fault tolerance, with byte limits as a ceiling. Streams are provisioned centrally by a deploy-time job with an idempotent upsert; consumers are created per subscriber at subscribe time."
Next: the reader side — consumers: push vs pull, durable vs ephemeral, and the QueueSubscribe your services actually call. Ask me about the retention policies if the interest model is new.

1. docs.nats.io — JetStream.

2. docs.nats.io — Streams. In-repo: cmd/server/fink/streams/notificationmgmt.go, jetstream.go:360-403.