Firebase App component with launcher lifecycle and health check integration.
What's included:
- Config with ProjectID (FIREBASE_PROJECT_ID env var); credentials via ADC
- Provider interface exposing native *firebase.App directly
- Component interface: launcher.Component + health.Checkable + Provider
- New(logger, cfg) constructor for lifecycle registration via lc.Append
- Health check via GetUser("health-probe-non-existent") + auth.IsUserNotFound at LevelCritical
- No-op OnStop (Firebase Admin SDK has no Close method)
Tested-via: todo-api POC integration
Reviewed-against: docs/adr/
3.6 KiB
3.6 KiB
firebase
Firebase App component with launcher lifecycle and health check integration.
Purpose
Manages the lifecycle of a firebase.google.com/go/v4 App: validates config at init time,
exposes the native *fb.App to consumers, and performs health checks by probing the Auth
service. Does not wrap or abstract any Firebase service beyond App construction.
Tier & Dependencies
Tier 3 (infrastructure) — depends on:
code.nochebuena.dev/go/health(Tier 2)code.nochebuena.dev/go/launcher(Tier 2)code.nochebuena.dev/go/logz(Tier 1)firebase.google.com/go/v4(Firebase Admin SDK, wraps gRPC/HTTP internally)
Key Design Decisions
- Health check via known-nonexistent UID:
HealthCheckcallsGetUserwith the UID"health-probe-non-existent"and usesauth.IsUserNotFound(err)to distinguish a healthy "not found" response from a real connectivity failure. Avoids string matching on error messages. See ADR-001. - SDK lifecycle management:
OnInitcallsfb.NewApp.OnStopis a no-op log because the SDK has noClosemethod. See ADR-002. - App exposed directly:
App() *fb.Appgives consumers the full SDK entry point. They callapp.Auth(ctx)orapp.Firestore(ctx)themselves. The module does not cache or pre-construct service-specific clients. - Health priority is Critical:
Priority()returnshealth.LevelCritical. A Firebase Auth outage is treated as a critical failure. - Duck-typed Logger: The internal
loggerfield is typed aslogz.Logger, the shared interface from thelogzmodule (ADR-001 global pattern).
Patterns
Lifecycle registration:
fb := firebase.New(logger, cfg)
lc.Append(fb) // registers OnInit / OnStart / OnStop
Obtaining service clients from consumers:
type authService struct {
provider firebase.Provider
}
func (s *authService) VerifyToken(ctx context.Context, token string) (*auth.Token, error) {
authClient, err := s.provider.App().Auth(ctx)
if err != nil {
return nil, err
}
return authClient.VerifyIDToken(ctx, token)
}
Health check registration:
health.Register(fbComponent) // satisfies health.Checkable via Name()/Priority()/HealthCheck()
What to Avoid
- Do not call
App()beforeOnInithas run — it returnsnil. Guard against this in tests or when constructing components manually outside a lifecycle manager. - Do not add service-specific methods (e.g.
AuthClient(),FirestoreClient()) to this module. Callers obtain them fromApp()directly, which keeps the module free of service proliferation. - Do not increase the health check polling frequency significantly. Each
HealthCheckcall makes a live API call to Firebase Auth. Poll at most every 30 seconds. - Do not rely on
OnStopfor graceful in-flight request drain. The SDK has no explicit close; in-flight Firebase calls at shutdown must be handled by request context deadlines in the caller.
Testing Notes
- Unit tests do not require a real Firebase project. They test structural invariants:
Name(),Priority(), nil-safety ofOnStop, and error on emptyProjectID. TestComponent_OnInit_MissingProjectIDverifies thatOnInitreturns an error before attempting SDK initialisation whenProjectIDis empty.TestComponent_App_ReturnsNilBeforeInitconfirmsApp()is nil beforeOnInitis called.- Integration tests (real token verification, real health check) require Firebase credentials and belong outside this module.
compliance_test.go(packagefirebase_test) assertsNew(...)satisfiesComponentat compile time.