Where to go next
You started this chapter with a hand-rolled request-reply responder: a
subscriber on orders.inventory.check that read a request and published a
reply. You end it with OrderInventory, a named, versioned service that
answers the same subject, announces itself on $SRV, counts every request,
and load-balances across as many instances as you start. The behavior on the
wire never changed; the framework added everything around it.
This page doesn't teach anything new. It collects the model you built into one place and points you at the chapters and Reference that take it further.
The core idea
Every page in this chapter circled the same idea, which is worth restating on its own.
A service is a request-reply responder the framework formalizes. You hand it a name and a version, and it auto-generates a unique service ID, subscribes the discovery verbs, and tracks stats for you. The responder you already knew how to write now has an identity.
An endpoint is a named handler on a subject, and a group is a
subject-prefix namespace that organizes endpoints. One service can expose
many endpoints; a group makes them answer on {prefix}.{endpoint}. This is
how one running service covers more than one subject.
The three discovery verbs on $SRV (PING, INFO, and STATS) make a
service discoverable and observable without extra wiring. Ask $SRV.INFO
and every matching instance tells you its endpoints; ask $SRV.STATS and it
tells you its counters.
The default queue group "q" scales the service. Run more instances with
the same name and version, and the server delivers each request to exactly
one of them. There's no coordinator and no config; you scale by running more
instances.
Those four pieces are the service, the endpoint and group, discovery, and the queue group. Everything else in this chapter is a refinement of them.
Where the reference details live
This chapter is unversioned and concept-first. The exact config fields, their valid ranges, and the wire format of the discovery verbs live in Reference, which is versioned and exhaustive. When you need the precise type of a service config field, the full endpoint option list, or the JSON schema of an INFO response, that's where to look.
The Reference root is the entry point. The handoff phrases
throughout this chapter ("the full set of service configuration fields is
documented in Reference") all point into it. The $SRV wire format lives
under the client protocol reference.
Sibling deep dives
A service is built on top of pieces other chapters teach in full, and it leaves gaps that other chapters fill. Follow whichever one matches the question you're asking now.
The Core NATS deep dive covers the lower layer this one
builds on. Request-reply is the mechanism
every endpoint uses, and queue groups are
exactly the load balancing that the default group "q" gives you. If anything
in this chapter seemed to skip a step, that step is covered there.
The JetStream deep dive is where you go when a service needs to remember something. A service is at-most-once request-reply: it stores nothing, and when an instance stops, its in-flight work is gone. The moment your handler needs durable state, say an order that must survive a restart, that state belongs in a stream, not in the service.
The Resilient Clients deep dive covers the
connection underneath every instance: reconnect, drain beyond the framework's
Stop(), slow-consumer handling, and error callbacks. This chapter assumed a
healthy local connection; production connections aren't always healthy.
The Security deep dive covers subject isolation for $SRV
subjects, account access control, and cross-account service placement.
The Monitoring deep dive goes past per-endpoint stats to the server-side view: service-latency advisories and the metrics that tell you a service is slow before a caller complains.
The Topologies deep dive covers running services across
more than one server: how $SRV and service subjects propagate across leaf
nodes and gateways, and how to place instances in more than one region.
Where you are
This is the end of the chapter. The whole arc is complete, and no new
scenario state is introduced here. Your OrderInventory service (with its
check endpoint on orders.inventory.check) and your ShippingQuote
service (with its quote endpoint on shipping.quote) are still running in
your session exactly as you left them on the previous page, load-balancing
across whatever instances you started. You can keep experimenting, or stop
them gracefully when you're done; each Stop() drains in-flight requests
and unsubscribes the discovery verbs.
You hold the core model: a service is a named, versioned, discoverable
request-reply responder; endpoints and groups organize it; the $SRV verbs
make it discoverable and observable; and the queue group scales it. That model
is the foundation for everything else you'll build on the framework.
Production checklist
Every page in this chapter closed with a Pitfalls section. This collects the action items from all of them in one place: a last pass before you trust a service with real orders. Each group links back to the page that explains the why.
Your first service — see Pitfalls
- Unmarshal
req.Data()inside the handler and respond with a service error on bad input, rather than letting a malformed request crash the handler. - Pick a
Namethat matches the allowed character set and aVersionthat's valid SemVer; an invalid value fails service creation outright.
Endpoints and groups — see Pitfalls
- Override an endpoint's queue group only when you mean to change who load-balances with whom; the change is silent and easy to get wrong.
- Remember that disabling the queue group turns an endpoint into broadcast (every instance answers) instead of load balancing.
- Treat endpoints and metadata as immutable once added; there's no remove, so name and shape them deliberately the first time.
Discovery — see Pitfalls
- Collect discovery responses with a deadline-or-count loop; discovery is broadcast, so a single request returns only one instance, not all of them.
- Treat
$SRVas a reserved subject prefix; never publish to it yourself, or you collide with the framework's control plane.
Observability — see Pitfalls
- Check
Nats-Service-Error-Codeon every reply; a service error rides in headers, not as a transport failure, so an unchecked failed call looks like success. - Aggregate stats across instance IDs yourself; counters are per-instance, and
Reset()zeroes them.
Scaling — see Pitfalls
- Protect any external state your instances share (a database or a stream) rather than assuming a single instance; the framework holds no shared memory across instances.
- Keep handlers non-blocking, or run more instances; a blocking handler stalls every other request on that instance.
See also
- Reference — every service config field, endpoint option,
and
$SRVwire schema, versioned and exhaustive. - Core NATS deep dive — the request-reply and queue-group foundation this chapter is built on.
- JetStream deep dive — where service state goes when it has to survive a restart.