61 lines
2.7 KiB
Markdown
61 lines
2.7 KiB
Markdown
|
|
# 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 `Close` method on `App`).
|
||
|
|
- Difficulty in testing components that depend on Firebase.
|
||
|
|
|
||
|
|
## Decision
|
||
|
|
|
||
|
|
`firebaseComponent` implements `launcher.Component` with three lifecycle methods:
|
||
|
|
|
||
|
|
- **`OnInit`**: Validates that `Config.ProjectID` is non-empty, then calls `fb.NewApp` to
|
||
|
|
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 no `Close` method 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:
|
||
|
|
|
||
|
|
```go
|
||
|
|
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` + `OnStop` cover 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 `OnStop` method 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()` returns `nil` before `OnInit` is called. Consumers must not call `App()` before
|
||
|
|
the lifecycle has been initialised (this is verified by `TestComponent_App_ReturnsNilBeforeInit`).
|