// Package health implementiert Core OPS-01: Health-/Readiness-Endpunkte, die // echte Abhaengigkeiten (DB, Job-Queue) statt nur den Prozessstatus pruefen // — wiederverwendbar von Core UND jedem registrierten Modul (siehe API-02), // nicht nur von Core selbst. package health import ( "context" "time" ) // Checker prueft EINE Abhaengigkeit (z.B. Datenbank, Job-Queue). type Checker interface { Check(ctx context.Context) error } type CheckerFunc func(ctx context.Context) error func (f CheckerFunc) Check(ctx context.Context) error { return f(ctx) } // DefaultCheckTimeout begrenzt, wie lange EIN einzelner Check maximal // dauern darf, bevor er als fehlgeschlagen gilt — verhindert, dass ein // haengender Check den gesamten Readiness-Endpunkt blockiert // (Akzeptanzkriterium 2 / Pruefung 2: Antwort innerhalb definierter Zeit). const DefaultCheckTimeout = 2 * time.Second // Registry haelt alle benannten Checks eines Dienstes. type Registry struct { checks map[string]Checker timeout time.Duration } func NewRegistry() *Registry { return &Registry{checks: make(map[string]Checker), timeout: DefaultCheckTimeout} } func (r *Registry) WithTimeout(d time.Duration) *Registry { return &Registry{checks: r.checks, timeout: d} } // Register fuegt einen benannten Check hinzu (z.B. "database", "queue"). func (r *Registry) Register(name string, c Checker) { r.checks[name] = c } // Result ist der Ausgang eines einzelnen Checks. type Result struct { OK bool Error string } // CheckAll fuehrt alle registrierten Checks NEBENLAEUFIG mit je eigenem // Timeout aus (Akzeptanzkriterium 1: echte Abhaengigkeiten statt Prozess- // status) und liefert ready=false, sobald irgendein Check fehlschlaegt // (Akzeptanzkriterium 3: ein Ausfall wird sichtbar). func (r *Registry) CheckAll(ctx context.Context) (ready bool, results map[string]Result) { type namedResult struct { name string result Result } ch := make(chan namedResult, len(r.checks)) for name, checker := range r.checks { go func(name string, checker Checker) { checkCtx, cancel := context.WithTimeout(ctx, r.timeout) defer cancel() err := checker.Check(checkCtx) if err != nil { ch <- namedResult{name, Result{OK: false, Error: err.Error()}} return } ch <- namedResult{name, Result{OK: true}} }(name, checker) } results = make(map[string]Result, len(r.checks)) ready = true for i := 0; i < len(r.checks); i++ { nr := <-ch results[nr.name] = nr.result if !nr.result.OK { ready = false } } return ready, results }