Files
firebase/CLAUDE.md

87 lines
3.6 KiB
Markdown
Raw Permalink Normal View History

# 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**: `HealthCheck` calls `GetUser` with the UID
`"health-probe-non-existent"` and uses `auth.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**: `OnInit` calls `fb.NewApp`. `OnStop` is a no-op log
because the SDK has no `Close` method. See ADR-002.
- **App exposed directly**: `App() *fb.App` gives consumers the full SDK entry point.
They call `app.Auth(ctx)` or `app.Firestore(ctx)` themselves. The module does not
cache or pre-construct service-specific clients.
- **Health priority is Critical**: `Priority()` returns `health.LevelCritical`. A Firebase
Auth outage is treated as a critical failure.
- **Duck-typed Logger**: The internal `logger` field is typed as `logz.Logger`, the shared
interface from the `logz` module (ADR-001 global pattern).
## Patterns
**Lifecycle registration:**
```go
fb := firebase.New(logger, cfg)
lc.Append(fb) // registers OnInit / OnStart / OnStop
```
**Obtaining service clients from consumers:**
```go
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:**
```go
health.Register(fbComponent) // satisfies health.Checkable via Name()/Priority()/HealthCheck()
```
## What to Avoid
- Do not call `App()` before `OnInit` has run — it returns `nil`. 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 from `App()` directly, which keeps the module free of service
proliferation.
- Do not increase the health check polling frequency significantly. Each `HealthCheck` call
makes a live API call to Firebase Auth. Poll at most every 30 seconds.
- Do not rely on `OnStop` for 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 of `OnStop`, and error on empty `ProjectID`.
- `TestComponent_OnInit_MissingProjectID` verifies that `OnInit` returns an error before
attempting SDK initialisation when `ProjectID` is empty.
- `TestComponent_App_ReturnsNilBeforeInit` confirms `App()` is nil before `OnInit` is called.
- Integration tests (real token verification, real health check) require Firebase credentials
and belong outside this module.
- `compliance_test.go` (package `firebase_test`) asserts `New(...)` satisfies `Component`
at compile time.