srvc
Package overview, exported API, and runnable examples extracted from the latest tagged release.
v1.13.0
A tiny Go library for composing service modules with a clean lifecycle.
go get github.com/go-srvc/srvc@latest
Real services run multiple long-lived components (HTTP, DB, signals, telemetry). Wiring up their startup order, graceful shutdown, panic recovery, and error reporting by hand is repetitive and easy to get wrong.
Every module implements Init, Run, Stop, and ID. The runner sequences and supervises them so your main stays a list of components. Plain functions slot in through InitMod, RunMod, StopMod, CtxMod, and IdleMod. Optional toggles any module with a flag.
Panics are recovered. Errors from Init, Run, and Stop are aggregated. Stop runs in reverse order on every initialised module, even when something failed mid-flight.
The srvc package is standard library only: the runner, the Module interface, and thin function adapters. Pull in just the modules you need from go-srvc/mods: HTTP, SQL, OpenTelemetry, signals, tickers.
When any Run returns, the whole service shuts down. Pair with sigmod for clean SIGINT and SIGTERM handling, and let RunAndExit drop the exit code.
Four modules going through srvc's lifecycle under different scenarios. One row plays at a time; hover any row to lock the animation to it.
Signal triggers shutdown. Modules stop in reverse order.
M3's Init fails. M1 and M2, the already-initialised modules, get Stop in reverse. M4 never starts.
M3 panics during Run. The panic is recovered as an error and the rest shut down gracefully.
A full HTTP service with OpenTelemetry logs, traces, and metrics, a Postgres pool, and graceful shutdown on SIGINT or SIGTERM. Modules compose, the runner supervises, your main stays declarative.
package main
import (
"fmt"
"net/http"
"os"
"github.com/XSAM/otelsql"
"github.com/go-srvc/mods/httpmod"
"github.com/go-srvc/mods/logmod"
"github.com/go-srvc/mods/metermod"
"github.com/go-srvc/mods/sigmod"
"github.com/go-srvc/mods/sqlxmod"
"github.com/go-srvc/mods/tracemod"
"github.com/go-srvc/srvc"
semconv "go.opentelemetry.io/otel/semconv/v1.30.0"
_ "github.com/jackc/pgx/v5/stdlib"
)
func main() {
db := sqlxmod.New(
sqlxmod.WithOtel("pgx", os.Getenv("DSN"),
otelsql.WithAttributes(semconv.DBSystemNamePostgreSQL),
),
)
srvc.RunAndExit(
logmod.New(),
tracemod.New(),
srvc.Optional(os.Getenv("METRICS") == "1", metermod.New()),
sigmod.New(),
db,
httpmod.New(
httpmod.WithAddr(":8080"),
httpmod.WithHandler(handler(db)),
),
)
}
func handler(db *sqlxmod.DB) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var version string
if err := db.DB().GetContext(r.Context(), &version, "SELECT version()"); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "db version: %s\n", version)
})
}
Not everything deserves a struct. srvc ships five thin adapters that turn plain functions into modules. They are glue only: no options, no defaults, no logic of their own. Anything that wraps a real dependency belongs in a proper module, yours or one from go-srvc/mods.
package main
import (
"context"
"github.com/go-srvc/srvc"
)
func main() {
srvc.RunAndExit(
// Called during init, Run blocks until shutdown.
srvc.InitMod("migrations", runMigrations),
// Init and stop pair without any work of its own during run.
srvc.IdleMod("cache", openCache, closeCache),
// Called during run with a context that is canceled on shutdown.
srvc.CtxMod("consumer", consume),
// Called during run, service shuts down once it returns.
srvc.RunMod("job", doWork),
// Called during stop, in reverse order like any other module.
srvc.StopMod("flush", flushBuffers),
)
}
func runMigrations() error { return nil }
func openCache() error { return nil }
func closeCache() error { return nil }
func doWork() error { return nil }
func flushBuffers() error { return nil }
func consume(ctx context.Context) error {
<-ctx.Done()
return nil
}
RunMod and CtxMod run their function during the run phase and end the service when it returns; CtxMod cancels the context on Stop. InitMod, StopMod, and IdleMod have no run-phase work, so their Run blocks until shutdown. Stop order stays reverse of the module list.
Optional toggles a module with a flag. A disabled module is dropped before the init phase, so none of its Init, Run, or Stop functions are called, and its ID is logged as disabled. The rest of the module list runs as if it was never there.
package main
import (
"net/http"
"os"
"github.com/go-srvc/mods/httpmod"
"github.com/go-srvc/mods/metermod"
"github.com/go-srvc/mods/sigmod"
"github.com/go-srvc/srvc"
)
func main() {
srvc.RunAndExit(
sigmod.New(),
// Dropped before init when METRICS is unset, logged as disabled.
srvc.Optional(os.Getenv("METRICS") == "1", metermod.New()),
srvc.Optional(os.Getenv("DEBUG") == "1", debugServer()),
httpmod.New(httpmod.WithAddr(":8080")),
)
}
func debugServer() srvc.Module {
return httpmod.New(
httpmod.WithAddr("localhost:6060"),
httpmod.WithHandler(http.DefaultServeMux),
)
}
The module is still constructed by the caller, so keep heavy work in Init rather than the constructor. A disabled module may be nil. Enabled modules keep their position, so init and stop order stay predictable regardless of which flags are set.
Package overview, exported API, and runnable examples extracted from the latest tagged release.
v1.13.0
Ready-made modules for HTTP, SQL, OpenTelemetry, signals, and tickers. Pick a tagged release on each mod's page.
8 modules