package httpapi import ( "log/slog" "net/http" "github.com/syncova/syncova/packages/platform/config" "github.com/syncova/syncova/packages/platform/health" ) // healthHandler bedient die Betriebs- und Systemzustands-Endpunkte // (SYNCOVA_API.md §23, PROMPT.md §93/§94). type healthHandler struct { // healthRegistry liefert den Zustand aller überwachten Komponenten. healthRegistry *health.Registry // logger protokolliert Zustandsabfragen und Fehler. logger *slog.Logger // buildVersion ist die ausgelieferte Programmversion. buildVersion string // environment ist die Betriebsumgebung des Dienstes. environment config.Environment } // livenessResponse ist die Antwort auf eine Liveness-Prüfung. type livenessResponse struct { // Status ist "alive", sobald der Prozess Requests bedienen kann. Status string `json:"status"` } // readinessResponse ist die Antwort auf eine Readiness-Prüfung. type readinessResponse struct { // Ready meldet, ob der Dienst Verkehr annehmen darf. Ready bool `json:"ready"` // Status ist der Gesamtzustand über alle Komponenten. Status health.Status `json:"status"` } // systemHealthResponse ist der ausführliche Systemzustand. type systemHealthResponse struct { // Status ist der schlechteste Zustand aller Komponenten. Status health.Status `json:"status"` // Components enthält das Ergebnis je Komponente. Components map[string]health.CheckResult `json:"components"` // Version ist die laufende Programmversion. Version string `json:"version"` // Environment ist die Betriebsumgebung. Environment string `json:"environment"` } // handleLiveness beantwortet GET /health/live. // // Liveness beantwortet ausschließlich die Frage, ob der Prozess selbst arbeitet. // Sie prüft bewusst keine Abhängigkeiten: sonst würde eine kurzzeitig nicht // erreichbare Datenbank einen Neustart des Dienstes auslösen. func (handler *healthHandler) handleLiveness(responseWriter http.ResponseWriter, request *http.Request) { WriteSuccess(responseWriter, request, http.StatusOK, livenessResponse{Status: "alive"}) } // handleReadiness beantwortet GET /health/ready. // // Readiness prüft alle als kritisch markierten Komponenten. Ist eine davon // gestört, liefert der Endpunkt 503, damit kein Verkehr zugestellt wird. func (handler *healthHandler) handleReadiness(responseWriter http.ResponseWriter, request *http.Request) { isReady, systemReport := handler.healthRegistry.IsReady(request.Context()) responseStatusCode := http.StatusOK if !isReady { responseStatusCode = http.StatusServiceUnavailable } WriteSuccess(responseWriter, request, responseStatusCode, readinessResponse{ Ready: isReady, Status: systemReport.Status, }) } // handleSystemHealth beantwortet GET /api/v1/health mit dem Komponentenbericht. func (handler *healthHandler) handleSystemHealth(responseWriter http.ResponseWriter, request *http.Request) { systemReport := handler.healthRegistry.Check(request.Context()) // Der HTTP-Status folgt dem Gesamtzustand: ein kritischer Bericht darf nicht // mit 200 quittiert werden, sonst übersieht ihn jedes externe Monitoring. responseStatusCode := http.StatusOK if systemReport.Status == health.StatusCritical || systemReport.Status == health.StatusOffline { responseStatusCode = http.StatusServiceUnavailable } WriteSuccess(responseWriter, request, responseStatusCode, systemHealthResponse{ Status: systemReport.Status, Components: systemReport.Components, Version: handler.buildVersion, Environment: string(handler.environment), }) }