Co-authored-by: Gabriel Radureau <arcodange@gmail.com> Co-committed-by: Gabriel Radureau <arcodange@gmail.com>
201 lines
7.7 KiB
Markdown
201 lines
7.7 KiB
Markdown
# 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",
|
|
"go_version": "go1.26.1"
|
|
}
|
|
```
|
|
|
|
The `go_version` field provides the Go runtime version via `runtime.Version()`, useful for ops debugging (e.g., identifying which Go version is running in production).
|
|
|
|
### 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
|