Lesson 9 · The repo in practice · your service
conversationmgmt subscribers
Your service, reacting to the rest of the platform — three subscribers that keep the chat vendor in sync with users, staff, and groups it never talks to directly.
Your win: read conversationmgmt's real subscribers end to end — how they register, what they consume, and why their config differs — using everything from Parts 1–2.
Registration: where the subscribers wire up
Recall RegisterNatsSubscribers (Lesson 5) — conversationmgmt's is two registrars,
each starting a family of subscribers:
agora := agora_usermgmt_nats.NewSubscribersRegistered(rsc.DBWith("tom"), rsc.NATS(), …)
agora.StartSubscribeForAllSubscribers() // User.Created, Staff.Upserted
convo := convo_nats.NewSubscribersRegistered(rsc.DBWith("tom"), rsc.NATS(), …)
convo.StartSubscribeForAllSubscribers() // UserGroup.Upserted, Staff.Upserted
rsc.NATS() and rsc.DBWith("tom") but declares
no streams — because it doesn't publish domain events into JetStream (it
only publishes Notification.Created to another service's stream, Lesson 10). It
consumes streams that usermgmt owns (user, staff,
usergroup). This is the fan-out model from Lesson 1: usermgmt publishes a domain event
once; conversationmgmt (and others) react. Your service is a reactor.
A subscriber is a struct + StartSubscribe + a handler
Every subscriber follows the same tiny shape. Here's the one that reacts to a new user:
func (s *UserCreatedSubscriber) StartSubscribe() error {
opts := nats.Option{ JetStreamOptions: []nats.JSSubOption{
nats.ManualAck(),
nats.Bind(constants.StreamUser, constants.DurableConversationMgmtUserCreated), // usermgmt's stream
nats.DeliverSubject(constants.DeliverConversationMgmtUserCreated),
nats.MaxDeliver(3), nats.AckWait(30*time.Second), nats.MaxAckPending(30),
}}
return s.nats.QueueSubscribe(constants.SubjectUserCreated, // "User.Created"
constants.QueueConversationMgmtUserCreated, opts, s.Handle) // s.Handle = create the user in Agora chat
}
The Handle callback is your MsgHandler (Lesson 7): when usermgmt creates
a user, conversationmgmt gets the event and creates the matching user in the Agora chat vendor — so
the chat system stays in sync with a service it has no direct call to.
Three subscribers, three tunings
The three conversationmgmt subscribers are the same shape with config tuned to the work:
| Subscriber | Subject / Stream | Tuning |
|---|---|---|
UserCreated | User.Created / user | MaxDeliver 3, AckWait 30s |
StaffUpserted | Staff.Upserted / staff | MaxDeliver 10, DeliverNew, SkipMsgOlderThan aWeekAgo |
UserGroupUpdate | UserGroup.Upserted / usergroup | MaxDeliver 10, AckWait 300s, DeliverNew, aWeekAgo |
The UserGroupUpdate handler does more work (reconciling group membership), so it gets
a longer AckWait (300s) and more retries (10). Both group/staff subscribers use
DeliverNew (don't replay history) and SkipMsgOlderThan aWeekAgo (Lesson 8 —
ignore events older than a week, so a redeploy after downtime doesn't reprocess a flood). Same
machinery, tuned per workload.
conversation/controller/nats/subscribers_registered.go and you'll find two
StudentCourseClassSynced subscribers commented out, with a note that they
moved to Kafka (Lesson 12). So your own service is a live example of the NATS→Kafka migration: some
events it used to consume over NATS now arrive over Kafka, with the domain handler reused. When you
see a commented-out subscriber, that's what happened.
Your own subscribers
Open the real files — they're short, and they're the template for anything you'll add.
→ in-repo internal/conversationmgmt/modules/agora_usermgmt/controller/nats/ +
…/conversation/controller/nats/
→ docs.nats.io — Consumers (the config you're reading)
Check yourself (from memory)
Q1. Why does conversationmgmt declare no NATS streams?
Q2. Why does UserGroupUpdate use a 300s AckWait vs UserCreated's 30s?
AckWait is tuned to the work: a slower handler needs a
longer window before the server assumes failure and redelivers.
Q3. A subscriber in this repo is made of…
StartSubscribe (the QueueSubscribe with tuned
options) + a MsgHandler. Every subscriber is a variation on this.
RegisterNatsSubscribers
(gserver.go:168-179) → two registrars (agora + conversation), each
StartSubscribeForAllSubscribers. Subscriber-only — declares no streams;
consumes usermgmt's user/staff/usergroup
streams (fan-out: usermgmt publishes, convo reacts). Shape: struct +
StartSubscribe (QueueSubscribe(subject, queue, opts, Handle)) + a handler.
Three: UserCreated (User.Created; MaxDeliver 3, AckWait 30s; creates
the Agora chat user) · StaffUpserted (Staff.Upserted; MaxDeliver 10, DeliverNew,
SkipMsgOlderThan aWeekAgo) · UserGroupUpdate (UserGroup.Upserted; MaxDeliver 10, AckWait
300s, DeliverNew, aWeekAgo — heavier handler → longer window). Some subs (StudentCourseClassSynced)
are commented out — migrated to Kafka.DeliverNew
plus a stale-message skip so a redeploy doesn't reprocess history."
1. In-repo: …/agora_usermgmt/controller/nats/user_created_subscriber.go:28-44, …/conversation/controller/nats/subscribers_registered.go, cmd/server/conversationmgmt/gserver.go:168-179.