Skip to content

Health Primer

Open this page after Stable Roots when the change still belongs to the default service path and the real question has narrowed to how the service reports its own health: what types describe liveness vs. readiness, how individual components signal their state, and what the canonical health model looks like.

health owns health status types, readiness helpers, and component health models. It is intentionally not an HTTP package and not an ops analytics package. HTTP health endpoints live in the owning service’s handler layer; orchestration lives in reference apps or extension families.

checker := health.ComponentCheckerFunc(func(ctx context.Context) health.HealthState {
if err := db.PingContext(ctx); err != nil {
return health.StateUnhealthy
}
return health.StateHealthy
})
agg := health.NewAggregate(checker)
  • you are defining or checking HealthStatus or HealthState values
  • you are implementing ComponentChecker for a dependency (database, cache, downstream service)
  • you are building a ComponentHealth aggregate from multiple checkers
  • you are reading or writing ReadinessStatus in a readiness probe handler
  • the change is about the HTTP endpoint that exposes health — that belongs in the handler layer of your service
  • the change introduces check orchestration, retry policy, or health manager abstractions
  • the work is about build metadata exposure, health history retention, or trend reporting
  • the change adds metrics, analytics, or export backends — those belong in x/observability

First files to read in the current repository

Section titled “First files to read in the current repository”
  1. health/module.yaml
  2. health/core.go
  3. health/readiness.go
  4. reference/standard-service/internal/handler/health.go
Keep it in health when the work is aboutMove out when the work becomes
HealthStatus — the typed enum: Healthy, Degraded, Unhealthyfeature-specific status catalogs with domain vocabulary
ComponentChecker interface and ComponentHealth aggregatehealth manager that executes checks on a schedule with retry
ReadinessStatus — a small explicit type for readiness probesHTTP endpoint wiring, transport-specific response shaping
Checking a single component’s state inside a handlerops reporting, health history export, or trend analytics
import "github.com/spcent/plumego/health"

The canonical pattern is to expose /healthz (liveness) and /readyz (readiness) from your handler layer, using health types to model the state:

type HealthHandler struct {
ServiceName string
db health.ComponentChecker
}
func (h HealthHandler) Live(w http.ResponseWriter, r *http.Request) {
contract.WriteResponse(w, r, http.StatusOK, map[string]string{
"status": "ok",
"service": h.ServiceName,
}, nil)
}
func (h HealthHandler) Ready(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
status := health.ReadinessStatus{
Ready: true,
Timestamp: time.Now(),
Components: map[string]bool{},
}
if err := h.db.Check(ctx); err != nil {
status.Ready = false
status.Reason = "database unavailable"
status.Components["db"] = false
contract.WriteResponse(w, r, http.StatusServiceUnavailable, status, nil)
return
}
status.Components["db"] = true
contract.WriteResponse(w, r, http.StatusOK, status, nil)
}
type DBChecker struct {
db *sql.DB
}
func (c DBChecker) Name() string { return "database" }
func (c DBChecker) Check(ctx context.Context) error {
return c.db.PingContext(ctx)
}
ConstantMeaningReady to serve?
health.StatusHealthyAll systems nominalYes
health.StatusDegradedPartial failure, degraded serviceYes
health.StatusUnhealthyComponent down, cannot serveNo
state := health.StatusDegraded
if state.IsReady() {
// service can accept traffic
}
healthH := handler.HealthHandler{
ServiceName: "my-service",
db: DBChecker{db: sqlDB},
}
app.Get("/healthz", http.HandlerFunc(healthH.Live))
app.Get("/readyz", http.HandlerFunc(healthH.Ready))

health is a support package, not a feature catalog. Services frequently conflate “the type that represents health” with “the system that manages and exposes health.” Keeping health as pure types and checkers means the same model works whether health is exposed over HTTP, gRPC, or a metrics scrape endpoint — the format is not the model’s concern.