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 about Move out when the work becomes
HealthStatus — the typed enum: Healthy, Degraded, Unhealthy feature-specific status catalogs with domain vocabulary
ComponentChecker interface and ComponentHealth aggregate health manager that executes checks on a schedule with retry
ReadinessStatus — a small explicit type for readiness probes HTTP endpoint wiring, transport-specific response shaping
Checking a single component’s state inside a handler ops 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)
}
Constant Meaning Ready to serve?
health.StatusHealthy All systems nominal Yes
health.StatusDegraded Partial failure, degraded service Yes
health.StatusUnhealthy Component down, cannot serve No
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.