✨ feat(server): /api/info aggregator + frontend version footer (#40)
Co-authored-by: Gabriel Radureau <arcodange@gmail.com> Co-committed-by: Gabriel Radureau <arcodange@gmail.com>
This commit was merged in pull request #40.
This commit is contained in:
197
adr/0026-composite-info-endpoint.md
Normal file
197
adr/0026-composite-info-endpoint.md
Normal file
@@ -0,0 +1,197 @@
|
||||
# ADR 0026: Composite Info Endpoint vs Separate Calls
|
||||
|
||||
**Status:** Implemented (2026-05-05 — PR pending)
|
||||
|
||||
## Context
|
||||
|
||||
The application currently exposes several endpoints that provide system information:
|
||||
- `/api/version` - returns version, commit, build date, Go version (cached 60s)
|
||||
- `/api/health` - returns `{"status":"healthy"}` (simple liveness)
|
||||
- `/api/healthz` - returns rich health info: status, version, uptime_seconds, timestamp
|
||||
- `/api/ready` - returns readiness with connection details
|
||||
|
||||
Frontend components like `HealthDashboard` currently call `/api/healthz` to display server info. However, there is a need for a **composite endpoint** that aggregates:
|
||||
1. Version information (from `/api/version`)
|
||||
2. Build metadata (commit hash, build date)
|
||||
3. Uptime information (from `/api/healthz`)
|
||||
4. Cache status (enabled/disabled)
|
||||
5. Health status
|
||||
|
||||
This raises an architectural question: **Should we create a new composite `/api/info` endpoint, or should frontend components make multiple separate API calls?**
|
||||
|
||||
### The Problem with Separate Calls
|
||||
|
||||
If the frontend makes individual calls to `/api/version`, `/api/healthz`, and checks cache config separately:
|
||||
|
||||
1. **Multiple network requests**: 3-4 HTTP round trips per page load
|
||||
2. **Inconsistent data**: Responses may come from different moments in time
|
||||
3. **No caching coordination**: Each endpoint has its own cache key and TTL
|
||||
4. **Complex frontend logic**: Need to merge data from multiple sources
|
||||
5. **Poor user experience**: Slower page loads, multiple loading states
|
||||
|
||||
### Current State Analysis
|
||||
|
||||
| Endpoint | Data Provided | Cache TTL | Use Case |
|
||||
|----------|---------------|-----------|----------|
|
||||
| `/api/version` | version, commit, built, go | 60s | Version info |
|
||||
| `/api/healthz` | status, version, uptime_seconds, timestamp | None | K8s probes, health dashboard |
|
||||
| `/api/health` | status: "healthy" | None | Simple liveness |
|
||||
| `/api/ready` | ready, connections, reason | None | Readiness probes |
|
||||
|
||||
The `/api/healthz` endpoint already combines some data (status + version + uptime + timestamp), but it:
|
||||
- Doesn't include commit_short
|
||||
- Doesn't include build_date separately
|
||||
- Doesn't include cache_enabled
|
||||
- Is not cached
|
||||
- Has Kubernetes-specific field naming (`healthz`)
|
||||
|
||||
## Decision Drivers
|
||||
|
||||
* **Performance**: Minimize network round trips for frontend
|
||||
* **Consistency**: All data should reflect the same point-in-time
|
||||
* **Maintainability**: Single source of truth for system info
|
||||
* **Caching**: Reuse existing cache infrastructure (ADR-0022)
|
||||
* **API Design**: Follow REST principles and existing patterns
|
||||
* **Backward Compatibility**: Existing endpoints must remain unchanged
|
||||
|
||||
## Considered Options
|
||||
|
||||
### Option 1: Composite `/api/info` Endpoint (Chosen)
|
||||
|
||||
Create a new endpoint that aggregates all required data in a single call.
|
||||
|
||||
**Pros:**
|
||||
- ✅ Single network request for frontend
|
||||
- ✅ Consistent point-in-time data
|
||||
- ✅ Can leverage existing cache infrastructure with key `info:json`
|
||||
- ✅ Follows existing pattern of `/api/version` caching
|
||||
- ✅ Clean API design - one endpoint, one purpose
|
||||
- ✅ Reduces frontend complexity
|
||||
- ✅ Better UX - faster page loads
|
||||
- ✅ Aligns with ADR-0022 cache strategy (reusable cache key pattern)
|
||||
|
||||
**Cons:**
|
||||
- ⚠️ Duplicates some data from `/api/healthz` and `/api/version`
|
||||
- ⚠️ Requires new endpoint implementation
|
||||
- ⚠️ Need to maintain consistency if source endpoints change
|
||||
|
||||
### Option 2: Frontend Aggregation with Multiple Calls
|
||||
|
||||
Frontend makes separate calls to `/api/version`, `/api/healthz`, and introspects config.
|
||||
|
||||
**Pros:**
|
||||
- ✅ No backend changes required
|
||||
- ✅ Uses existing endpoints
|
||||
|
||||
**Cons:**
|
||||
- ❌ Multiple network requests (3-4 round trips)
|
||||
- ❌ Inconsistent data timing
|
||||
- ❌ Complex error handling in frontend
|
||||
- ❌ Poor UX - multiple loading states, slower
|
||||
- ❌ Each endpoint has different caching behavior
|
||||
- ❌ Violates DRY - same data fetched multiple times
|
||||
|
||||
### Option 3: Extend `/api/healthz` Endpoint
|
||||
|
||||
Add `commit_short`, `build_date`, and `cache_enabled` fields to existing `/api/healthz`.
|
||||
|
||||
**Pros:**
|
||||
- ✅ Reuses existing endpoint
|
||||
- ✅ Single request
|
||||
|
||||
**Cons:**
|
||||
- ❌ Breaks backward compatibility (response schema change)
|
||||
- ❌ `/api/healthz` is Kubernetes-focused (naming convention)
|
||||
- ❌ Not cached currently
|
||||
- ❌ Mixes health probe concerns with version info
|
||||
- ❌ Violates single responsibility
|
||||
|
||||
### Option 4: GraphQL / Query Parameters
|
||||
|
||||
Allow clients to specify which fields they want via query parameters.
|
||||
|
||||
**Pros:**
|
||||
- ✅ Flexible - clients get exactly what they need
|
||||
- ✅ Single endpoint
|
||||
|
||||
**Cons:**
|
||||
- ❌ Overkill for this use case
|
||||
- ❌ Not consistent with existing REST API design
|
||||
- ❌ Complex implementation
|
||||
- ❌ Not aligned with project architecture (Chi router, REST style)
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
**Chosen: Option 1 - Composite `/api/info` Endpoint**
|
||||
|
||||
We will implement a new `GET /api/info` endpoint that returns a JSON object with all required fields in a single call. This endpoint will:
|
||||
|
||||
1. Aggregate data from existing sources (`version` package, `config`, server uptime)
|
||||
2. Be cached using the existing cache service with key `info:json`
|
||||
3. Use TTL from `config.cache.default_ttl_seconds` (consistent with ADR-0022)
|
||||
4. Return `X-Cache: HIT/MISS` headers for debugging
|
||||
5. Follow existing Go handler patterns from `pkg/server/server.go`
|
||||
|
||||
### Response Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "1.4.0",
|
||||
"commit_short": "a3f7b2c1",
|
||||
"build_date": "2026-05-04T08:00:00Z",
|
||||
"uptime_seconds": 1234,
|
||||
"cache_enabled": true,
|
||||
"healthz_status": "healthy"
|
||||
}
|
||||
```
|
||||
|
||||
### Rationale
|
||||
|
||||
1. **Performance**: Single HTTP request instead of 3-4 separate calls
|
||||
2. **Consistency**: All data reflects the same moment in time
|
||||
3. **Caching**: Leverages existing cache infrastructure (ADR-0022) with predictable key pattern
|
||||
4. **API Design**: Clean, RESTful endpoint with single responsibility
|
||||
5. **Maintainability**: Clear separation of concerns - info aggregation is a distinct use case
|
||||
6. **Backward Compatibility**: Existing endpoints remain unchanged
|
||||
7. **Frontend Simplicity**: Reduces complexity and improves UX
|
||||
|
||||
### Cache Strategy
|
||||
|
||||
Following ADR-0022 pattern:
|
||||
- Cache key: `info:json` (consistent with `version:format` pattern)
|
||||
- TTL: `config.cache.default_ttl_seconds` (default 300 seconds)
|
||||
- Cache service: `pkg/cache/cache.go` InMemoryService
|
||||
- Headers: `X-Cache: HIT` or `X-Cache: MISS`
|
||||
|
||||
This allows the endpoint to be fast even under load, while maintaining data freshness.
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
1. **Improved frontend performance**: Single request instead of multiple
|
||||
2. **Better UX**: Faster page loads, simpler loading states
|
||||
3. **Consistent data**: All fields reflect the same point-in-time
|
||||
4. **Cache efficiency**: Reuses existing cache infrastructure
|
||||
5. **Clean separation**: Info endpoint handles aggregation, source endpoints unchanged
|
||||
6. **Easy to test**: Single endpoint with predictable response
|
||||
|
||||
### Negative
|
||||
|
||||
1. **Data duplication**: Some fields appear in multiple endpoints
|
||||
2. **Maintenance burden**: If source data changes, endpoint must be updated
|
||||
3. **New endpoint**: Increases API surface area (though minimal)
|
||||
|
||||
### Mitigation
|
||||
|
||||
1. Data duplication is acceptable - it's read-only system info
|
||||
2. Source the data from the same packages/functions used by other endpoints
|
||||
3. The new endpoint has a clear, focused purpose
|
||||
|
||||
## Links
|
||||
|
||||
- [ADR-0002: Chi Router](adr/0002-chi-router.md) - Routing foundation
|
||||
- [ADR-0022: Rate Limiting Cache Strategy](adr/0022-rate-limiting-cache-strategy.md) - Cache pattern reference
|
||||
- [pkg/server/server.go](pkg/server/server.go) - Handler patterns
|
||||
- [pkg/cache/cache.go](pkg/cache/cache.go) - Cache service
|
||||
- [pkg/version/version.go](pkg/version/version.go) - Version data source
|
||||
Reference in New Issue
Block a user