Lesson 2 · Core NATS & the JetStream model

Subjects & pub/sub

How messages are addressed — a dotted hierarchy with wildcards — and the queue groups that let many subscribers share the load.

Your win: read the repo's subject names, use the two wildcards, and explain how a queue group turns "everyone gets it" into "exactly one of us gets it."

Subjects: the address

A subject is where a message is published — NATS's equivalent of a topic, but hierarchical and fine-grained. This repo names them dotted PascalCase, one per event:1

internal/golibs/constants/common.go
SubjectUserCreated          = "User.Created"
SubjectUpsertStaff          = "Staff.Upserted"
SubjectUpsertUserGroup      = "UserGroup.Upserted"
SubjectNotificationCreated  = "Notification.Created"
SubjectActivityLogCreated   = "ActivityLog.Created"

Every event name has a small family of constants beside it — Subject… (the address), Stream… (the store, Lesson 3), Queue…/Durable…/Deliver… (the consumer, Lesson 4). Learn to read the family and you can read any subscriber.

Publish/subscribe: one-to-many

The default NATS model: a publisher sends on a subject, and every subscriber interested in that subject receives a copy.1 The publisher doesn't know or care who's listening — total decoupling. In the repo, usermgmt publishes Staff.Upserted, and both conversationmgmt subscribers (agora + conversation) independently receive it, plus anyone else who cares.

Wildcards: subscribe to a family

Subjects are hierarchical, so subscriptions can match patterns. Two wildcards:2

WildcardMatchesExample
*exactly one tokenUser.*User.Created, User.Deleted (not User.Foo.Bar)
>one or more trailing tokens (the rest)chat.chat.> → everything beneath it
How wildcards are used here A JetStream stream captures with a wildcard — the user stream grabs User.*, so all user events land in one store. A consumer then filters down to a concrete subject — conversationmgmt's consumer sets FilterSubject = "User.Created" to receive only that one (Lesson 4). Capture broadly, consume narrowly.

Queue groups: load-balance the subscribers

Plain pub/sub sends every message to every subscriber — but if your service runs three replicas, you usually want each message handled once, not three times. A queue group does exactly that: subscribers sharing a queue-group name form a pool, and the server delivers each message to exactly one member (chosen for you).3

publish User.Created ──▶ [ replica-1 ] ┐ [ replica-2 ] ← │ queue group "queue-conversationmgmt-user-created" [ replica-3 ] ┘ → the server picks ONE to deliver to

This is how the repo scales a subscriber horizontally: every conversationmgmt pod joins the same queue group (Queue… constant), so three pods share the work rather than triplicating it. It's the reason nearly every subscription here is a QueueSubscribe, not a plain Subscribe.

Core NATS caveat, one more time Everything in this lesson is core NATS — subjects, pub/sub, queue groups exist without JetStream, and at this level delivery is at-most-once. JetStream (Lesson 3) keeps the same subject/queue-group vocabulary but adds the durable store behind it, upgrading delivery to at-least-once. Same addressing, persistent backing.
Backend use case Backend use case: this is the model behind subjects like User.Created and Notification.Created and the queue-group subscribers your services run.
Common mistake Common mistake: thinking a queue group means every subscriber gets the message when it actually means one member gets each delivery.
Read this next

Subjects, pub/sub & queue groups

The addressing model and the two distribution patterns — the vocabulary the rest of the course reuses.

docs.nats.io — Subjects & wildcards
Publish/Subscribe · Queue Groups

In plain English Plain English: subjects are the address system, and queue groups are how one message gets shared across workers instead of broadcast to all of them.

Check yourself (from memory)

Q1. What does the * wildcard match in a subject?

* = one token; > = the rest. Streams capture with */>; consumers filter to a concrete subject.

Q2. What does a queue group do?

Load balancing: one message → one member. It's how three replicas of a service share the work instead of triplicating it.

Q3. How do streams and consumers use wildcards differently?

Capture broadly, consume narrowly: the user stream grabs User.*; a consumer sets FilterSubject = "User.Created".
Recall: subjects, pub/sub, wildcards, queue groups.
address + one-to-many + wildcards + load-balance, then reveal
Subject = the message address; dotted PascalCase here (User.Created, Staff.Upserted, Notification.Created), each with a family (Subject/Stream/Queue/Durable/Deliver). Pub/sub = one-to-many: every interested subscriber gets a copy; publisher fully decoupled. Wildcards: * = one token (User.*); > = the rest (chat.chat.>). Streams capture broadly (User.*); consumers FilterSubject narrowly (User.Created). Queue group = each message → exactly one member (load balancing) → how N replicas share work; hence QueueSubscribe everywhere. All core NATS (at-most-once here); JetStream keeps the vocabulary + adds the store.
🎯 Interview one-liner "How does NATS address and distribute messages?" → "Messages are published to hierarchical dotted subjects; subscribers match exact subjects or wildcards — * for one token, > for the rest. Plain pub/sub is one-to-many, but a queue group load-balances: each message goes to exactly one member, so replicas of a service share the work. Our streams capture a wildcard and consumers filter to a concrete subject."
Next: turning fire-and-forget into durable — JetStream and streams: how messages get stored, the retention policies, and who provisions them. Ask me about wildcards or queue groups if they're fuzzy.

1. docs.nats.io — Publish/Subscribe. In-repo: internal/golibs/constants/common.go.

2. docs.nats.io — Subjects & wildcards.

3. docs.nats.io — Queue Groups.