Files
health/CLAUDE.md
Rene Nochebuena e1b6b7ddd7 feat(health): initial stable release v0.9.0
HTTP health check handler with parallel goroutine-per-check execution, 5 s request-derived timeout, and two-level criticality (LevelCritical → 503, LevelDegraded → 200).

What's included:
- `Checkable` interface (HealthCheck / Name / Priority) and `Level` type with LevelCritical and LevelDegraded constants
- `NewHandler(logger, checks...)` returning http.Handler; runs all checks concurrently via buffered channel, returns JSON with per-component status and latency
- `ComponentStatus` and `Response` types for the JSON response body

Tested-via: todo-api POC integration
Reviewed-against: docs/adr/
2026-03-18 14:06:17 -06:00

55 lines
3.3 KiB
Markdown

# health
HTTP health check handler with parallel checks, timeouts, and two-level criticality.
## Purpose
Provides a single `http.Handler` that interrogates any number of `Checkable` infrastructure components concurrently and returns a JSON response with per-component status and an overall service status. Designed to be mounted at `/health` and consumed by load balancers and orchestrators.
## Tier & Dependencies
**Tier 1** — depends only on Go stdlib (`context`, `encoding/json`, `net/http`, `time`). No external or internal module imports.
## Key Design Decisions
- **Parallel checks** (ADR-001): every registered component is checked in its own goroutine. A `context.WithTimeout(r.Context(), 5*time.Second)` provides the deadline for the entire request. The handler blocks until all goroutines report via a buffered channel.
- **Two-level criticality** (ADR-002): `LevelCritical` (value `0`) → `DOWN` + HTTP 503 on failure; `LevelDegraded` (value `1`) → `DEGRADED` + HTTP 200 on failure. The zero-value default is `LevelCritical`.
- **`Checkable` interface** (ADR-003): infrastructure components implement `HealthCheck(ctx) error`, `Name() string`, and `Priority() Level`. The health package defines the interface; infra packages satisfy it — not the reverse.
- **Duck-typed `Logger`** (global ADR-001): the `Logger` interface is defined locally in this package. Any logger that matches the method set (including `logz.Logger`) is accepted without an import of `logz`.
## Patterns
Construct the handler at app bootstrap and pass all `Checkable` components:
```go
db := postgres.New(logger, cfg) // satisfies health.Checkable
rdb := redis.New(logger, cfg) // satisfies health.Checkable
h := health.NewHandler(logger, db, rdb)
router.Get("/health", h)
```
Implement `Checkable` on a custom type:
```go
type myService struct{}
func (s *myService) HealthCheck(ctx context.Context) error { return s.ping(ctx) }
func (s *myService) Name() string { return "my-service" }
func (s *myService) Priority() health.Level { return health.LevelDegraded }
```
## What to Avoid
- Do not add a global/package-level handler or registry. `NewHandler` is the only constructor; use dependency injection.
- Do not call `NewHandler` with nil logger — the handler will panic on the first request when it calls `logger.WithContext`.
- Do not import infra modules (postgres, mysql, etc.) from this package. The dependency must flow one way: infra → health.
- Do not remove the buffered channel (`make(chan result, len(h.checks))`). Making it unbuffered would leak goroutines if the handler returns before draining all results.
## Testing Notes
- `health_test.go` covers: no checks (UP), all UP, critical DOWN (503), degraded DOWN (200), mixed DOWN+DEGRADED (503), parallel execution timing, JSON shape, and context timeout propagation.
- `compliance_test.go` contains compile-time interface satisfaction checks for `Logger` and `Checkable`. Run `go build ./...` to verify them without executing any runtime code.
- Tests use an in-process `httptest.NewRecorder` — no real network or infrastructure required.
- The parallelism test uses a 100 ms delay per check and asserts total elapsed < 300 ms. It is timing-sensitive; extremely slow CI runners may produce false negatives.