Ownership and teardown
Every route, named middleware, and event subscription is tagged with the service that registered it. That bookkeeping exists for one reason: a single service can be shut down at runtime without restarting the process.
app.CloseService("auth") // everything auth registered now returns 503app.RestartService("auth") // re-initializes and re-arms itWhat actually gets disabled
Section titled “What actually gets disabled”Everything the service registered is replaced with a static 503 in the
unified error shape. That means its routes, and also
every named middleware it registered, wherever that middleware is referenced,
including on routes owned by services that are still running.
router.Register("auth", "require-auth", requireAuth) // owned by "auth"
router.Route("/api", func(sub *gas.Router) { sub.Use(gas.MiddlewareByName("require-auth")) sub.Handle("billing", http.MethodGet, "/invoices", handler) // owned by "billing"})
app.CloseService("auth")// GET /api/invoices -> 503, even though "billing" is still running.Reacting to it
Section titled “Reacting to it”Other services can respond, which is how a system degrades deliberately rather than by accident.
gas.SubscribeWithOwner(bus, s.Name(), gas.SystemServiceClosed, func(data gas.SystemServiceClosedPayload) { // enter degraded mode if data.ServiceName was a dependency })Events
Section titled “Events”Events are typed, so payloads are checked at compile time rather than asserted at runtime.
var UserCreated = gas.Event[UserCreatedPayload]{Name: "user:created"}
type UserCreatedPayload struct{ Email string }
gas.SubscribeWithOwner(bus, s.Name(), UserCreated, func(data UserCreatedPayload) { // provision a billing account for data.Email})
gas.Emit(bus, UserCreated, UserCreatedPayload{Email: "user@example.com"}).Wait()Emit returns a *sync.WaitGroup, so the caller decides whether to wait for
handlers or move on. Subscribing with SubscribeWithOwner is what ties the
subscription to a service so teardown can remove it; an unowned subscription
lives until the process exits.
System events
Section titled “System events”| Event | Fired when |
|---|---|
gas.SystemServiceClosed |
A service is closed via CloseService |
gas.SystemServiceInitialized |
A service finishes Init, including on restart |
gas.SystemAllServicesInitialized |
Every service has initialized successfully |
gas.SystemShuttingDown |
A Worker or App begins shutdown; always fires |
gas.SystemServerShuttingDown |
The HTTP server begins graceful shutdown; App only |
gas.AppConfigUpdated |
App config is updated after binding; App only |
When to reach for this
Section titled “When to reach for this”Rarely, and that is fine. The kill-switch earns its keep when one subsystem is
failing in a way that threatens the rest, a dependency is down and its routes
should fail fast rather than pile up, or you are draining one feature ahead of a
deploy. If you never call CloseService, ownership tracking still pays for
itself by making the route map attributable: router.Routes() reports who owns
what.