Lesson 5 · Publishing, consuming & delivery
The golib & the connection
One shared client wraps all of NATS for the repo — and it connects lazily, the first time a service actually asks for it.
Your win: describe the JetStreamManagement wrapper, explain when and
how the NATS connection opens, and say how a service opts into NATS at all.
One wrapper for the whole repo
No service talks to raw nats.go. Everything goes through a single house client,
JetStreamManagement (internal/golibs/nats/jetstream.go) —
an interface that bundles every NATS operation the repo needs:
type JetStreamManagement interface {
ConnectToJS() // open the connection
UpsertStream(...) / UpsertConsumer(...) // provision (Lessons 3–4)
PublishContext / PublishAsyncContext / TracedPublish… // publish (Lesson 6)
Subscribe / QueueSubscribe / PullSubscribe // consume (Lesson 7)
RegisterReconnectHandler / RegisterDisconnectErrHandler / Close
}
So the wrapper is where all the repo-specific behaviour lives — the tenancy envelope, the tracing, the metrics, the idempotent upserts — and services just call these methods. The rest of Part 2 is really "what these methods do."
Opening the connection
ConnectToJS dials the server and turns the raw connection into a JetStream context:
conn, _ := nats.Connect(url,
nats.UserInfo(user, password), // per-service credentials (NATS ACLs, Lesson 12)
nats.ReconnectWait(...), nats.MaxReconnects(...),
nats.DisconnectErrHandler(...), nats.ReconnectHandler(...), // survive blips
nats.Timeout(5*time.Second))
js, _ := conn.JetStream(nats.PublishAsyncMaxPending(200000)) // ← big async-publish buffer
Two details worth keeping: it registers reconnect/disconnect handlers so a brief
network blip doesn't kill the service (and it flips a Prometheus disconnect gauge), and it sets
PublishAsyncMaxPending(200000) — a large buffer of in-flight async publishes (Lesson 6).
Lazy connect — on first use
You never call ConnectToJS yourself. You ask the resource container for the client —
rsc.NATS() — and it connects the first time, guarded by a
sync.Once (exactly the lazy pattern from the Go-techstack course):
func (r *Resources) GetNATS() (nats.JetStreamManagement, error) {
if r.natsjs == nil {
r.natsjsOnce.Do(func() { // ← runs once, ever
natsjs, _ := r.natsjser.NewJetStreamManagement(l, c) // build (doesn't connect)
natsjs.ConnectToJS() // ← the real connection, on first call
r.natsjs = natsjs
})
}
return r.natsjs, nil
}
So the connection opens the first time any code calls rsc.NATS() — a publisher, a
subscriber registration, or an interceptor. (rsc.NATS() panics if NATS config is
missing — fail-fast at boot, like every Resources client.)
How a service opts into NATS
NatsServicer —
RegisterNatsSubscribers(ctx, cfg, *Resources) error
(internal/golibs/bootstrap/nats.go:15-19). At boot, the framework detects the interface
and calls it, and that's where your service wires up every subscriber (Lesson 4's
QueueSubscribe calls live here). Your conversationmgmt and notification
gserver.go both implement it. Implement the interface → you get NATS subscribers; the
connection materializes lazily when you first touch rsc.NATS().
The NATS Go client
The nats.go client this wrapper is built on — Connect, options, and the
JetStream context.
→ pkg.go.dev — nats.go
→ docs.nats.io — connecting ·
in-repo internal/golibs/nats/jetstream.go:325-350
Check yourself (from memory)
Q1. When does the NATS connection actually open?
GetNATS builds + `ConnectToJS`s inside natsjsOnce.Do
on first use — the same lazy Resources pattern.
Q2. How does a service opt into having NATS subscribers?
QueueSubscribe calls live.
Q3. What do the reconnect/disconnect handlers on the connection provide?
JetStreamManagement interface
(internal/golibs/nats/jetstream.go) bundles all NATS ops (connect, upsert, publish,
subscribe, close) — all repo-specific behaviour lives here. Connect:
ConnectToJS = nats.Connect (per-service UserInfo, reconnect/disconnect
handlers, 5s timeout) → conn.JetStream(PublishAsyncMaxPending(200000)) (big async
buffer). Lazy: rsc.NATS() → GetNATS →
natsjsOnce.Do builds + connects on first use (panics if config missing).
Opt-in: a service implements NatsServicer.RegisterNatsSubscribers(ctx, cfg,
*Resources) — the framework calls it at boot; the QueueSubscribe calls live there.sync.Once, with reconnect handlers
for resilience and a large async-publish buffer. A service opts in by implementing a servicer
interface where it registers all its subscribers at boot."
1. nats.go. In-repo: internal/golibs/nats/jetstream.go:67-83,325-350, internal/golibs/bootstrap/resource.go:465-481.