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:

internal/golibs/nats/jetstream.go:67-83 (the interface)
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:

jetstream.go:325-350 (condensed)
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):

internal/golibs/bootstrap/resource.go:465-481 (GetNATS, condensed)
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

The servicer interface (from the Go-techstack course) A service uses NATS by implementing NatsServicerRegisterNatsSubscribers(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().
Read this next

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?

Implement the servicer interface; the framework calls it at boot, and that's where the QueueSubscribe calls live.

Q3. What do the reconnect/disconnect handlers on the connection provide?

They let the client survive a dropped connection and reconnect, flipping a Prometheus disconnect gauge along the way.
Recall: the golib wrapper + how it connects + how a service opts in.
wrapper + connect + lazy + opt-in, then reveal
Wrapper: one 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()GetNATSnatsjsOnce.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.
🎯 Interview one-liner "How do services connect to NATS?" → "Through one shared client that wraps the Go NATS library. The connection is lazy — it opens on first use via a 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."
Next: publishing — the envelope every message is wrapped in, why every publish gets a dedup id, and the traced variants. Ask me about the lazy connection if it's fuzzy.

1. nats.go. In-repo: internal/golibs/nats/jetstream.go:67-83,325-350, internal/golibs/bootstrap/resource.go:465-481.