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/
2.7 KiB
ADR-002: Firebase SDK Lifecycle Management via launcher.Component
Status: Accepted Date: 2026-03-18
Context
The firebase-admin-go SDK (firebase.google.com/go/v4) manages its own internal gRPC
connections, HTTP transports, and credential refresh cycles. The SDK entry point is a
*firebase.App instance, obtained via fb.NewApp(ctx, config). Service-specific clients
(Auth, Firestore, etc.) are obtained from the App on demand via app.Auth(ctx) and
app.Firestore(ctx).
Applications that construct the App outside of a lifecycle manager risk:
- Attempting to use the App before it is initialised.
- Not having a clear shutdown point (the SDK has no explicit
Closemethod onApp). - Difficulty in testing components that depend on Firebase.
Decision
firebaseComponent implements launcher.Component with three lifecycle methods:
-
OnInit: Validates thatConfig.ProjectIDis non-empty, then callsfb.NewAppto create the App and store it in the struct. Returns an error if project ID is missing or if the SDK fails to create the App. No network calls are made at this point — the SDK is lazy about establishing connections. -
OnStart: Logs that the app is ready. Currently a no-op beyond logging; the SDK does not require an explicit start call. -
OnStop: Logs shutdown. The firebase-admin-go SDK has noClosemethod on*fb.App. Connections managed by the SDK (gRPC channels, HTTP transport) are closed by the Go runtime's garbage collector when the App is no longer referenced. This is the accepted behaviour of the SDK.
Consumers access the App via App() *fb.App (the Provider interface) and then obtain
service-specific clients themselves:
authClient, err := component.App().Auth(ctx)
firestoreClient, err := component.App().Firestore(ctx)
This keeps the firebase module focused on App lifecycle without prescribing which Firebase
services consumers use.
Consequences
Positive:
- Firebase App creation is integrated into the application startup sequence. Failures (bad credentials, missing project ID) surface at startup, not at the first API call.
- The module is minimal:
OnInit+OnStart+OnStopcover the full SDK lifecycle. - Consumers are not limited to Auth; they can use any service the SDK supports.
Negative:
- There is no explicit SDK shutdown. The
OnStopmethod is effectively a log-and-return. In-flight requests to Firebase at shutdown time are handled by the SDK's own timeouts and the context cancellation of callers, not by this module. App()returnsnilbeforeOnInitis called. Consumers must not callApp()before the lifecycle has been initialised (this is verified byTestComponent_App_ReturnsNilBeforeInit).