Lesson 4 · Core NATS & the JetStream model
Consumers
The reader's cursor into a stream — push or pull, durable or not — and the one call your services use to create it.
Your win: explain what a consumer is, choose between push/pull and
durable/ephemeral, and read a real QueueSubscribe from your own service.
A consumer is a stateful cursor
A stream just stores messages. A consumer is how a subscriber reads them: a stateful cursor that tracks its position in the stream and which messages are still awaiting acknowledgement.1 Crucially, one stream can have many consumers, each at its own position — so conversationmgmt and notification can each read the same events independently, at their own pace. Stream = the log; consumer = your bookmark in it.
The three choices that define a consumer
Push vs pull
Push: the server delivers messages to you as they arrive. Pull:
you Fetch batches on your own schedule, controlling flow. This repo is
push everywhere — except one pull consumer (activity-log, Lesson 11).
Durable vs ephemeral
Durable: named, survives disconnects — a restarted service resumes
where it left off. Ephemeral: vanishes when the subscriber disconnects. The
repo's consumers are all durable (Durable… names).
Deliver policy
Where to start reading: DeliverNew (only messages after creation — the repo default, so a first deploy doesn't replay history), DeliverAll (from the start), DeliverLast, by time/sequence.
A real consumer: conversationmgmt's User.Created
Your services create their consumers by calling QueueSubscribe. Here's the real one
that reacts to a new user:
jsm.QueueSubscribe(
constants.SubjectUserCreated, // "User.Created" — the subject to receive
constants.QueueConversationMgmtUserCreated, // the queue group (load-balance replicas)
nats.Option{ JetStreamOptions: []JSSubOption{
nats.Bind(constants.StreamUser, constants.DurableConversationMgmtUserCreated), // stream + durable name
nats.DeliverSubject(constants.DeliverConversationMgmtUserCreated), // the push deliver subject
nats.ManualAck(), // we ack ourselves (at-least-once)
nats.MaxDeliver(3), // retry up to 3× then give up
nats.AckWait(30 * time.Second), // redeliver 30s after a no-ack
nats.MaxAckPending(30), // ≤30 unacked in flight (backpressure)
}},
handler, // creates the user in the Agora chat vendor
)
Read it as a sentence: "on subject User.Created, as a load-balanced group, bound to
the user stream with a durable named cursor, ack manually, retry up to 3 times 30 s
apart, at most 30 in flight." Every subscriber in the repo is a variation on this one shape — the
UserGroup.Upserted one adds DeliverNew() and SkipMsgOlderThan
and bumps to MaxDeliver(10) / AckWait(300s) (Lesson 4's deliver-policy card).
QueueSubscribe forces AckExplicit and
calls UpsertConsumer (with a 5-attempt retry) to create-or-reconcile
the durable consumer on the server — an idempotent upsert that deletes and recreates the
consumer if its config drifted (Lesson 8 details the ack side). So you declare the consumer's
config in the subscribe options, and the golib makes the server match — the same
git-as-config discipline you saw in the CDC course, applied to consumers.
JetStream consumers
Push vs pull, durable vs ephemeral, deliver & ack policies — the config you just read.
→ docs.nats.io — Consumers
→ Consumer details (ack policy, AckWait, MaxDeliver)
Check yourself (from memory)
Q1. What is a consumer, relative to a stream?
Q2. What does a durable consumer give you?
Q3. Why do the repo's consumers use DeliverNew()?
DeliverNew = begin at messages published after the
consumer is created — no surprise reprocessing of the stream's backlog.
DeliverNew (repo default — no history replay) / All / Last.
The call (QueueSubscribe): subject + queue group + options
Bind(stream, durable), DeliverNew, ManualAck,
MaxDeliver(3), AckWait(30s), MaxAckPending(30), handler. The
golib forces AckExplicit + calls UpsertConsumer (idempotent, delete+recreate
on drift). Stream stores; consumer reads.QueueSubscribe (L4). You can read the shape of any subscriber. Part 2 opens the
golib that makes it run.
1. docs.nats.io — Consumers. In-repo: …/agora_usermgmt/controller/nats/user_created_subscriber.go:28-45, jetstream.go:583-623.