Co-authored-by: Gabriel Radureau <arcodange@gmail.com> Co-committed-by: Gabriel Radureau <arcodange@gmail.com>
7.5 KiB
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:
- Version information (from
/api/version) - Build metadata (commit hash, build date)
- Uptime information (from
/api/healthz) - Cache status (enabled/disabled)
- 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:
- Multiple network requests: 3-4 HTTP round trips per page load
- Inconsistent data: Responses may come from different moments in time
- No caching coordination: Each endpoint has its own cache key and TTL
- Complex frontend logic: Need to merge data from multiple sources
- 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/versioncaching - ✅ 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/healthzand/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/healthzis 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:
- Aggregate data from existing sources (
versionpackage,config, server uptime) - Be cached using the existing cache service with key
info:json - Use TTL from
config.cache.default_ttl_seconds(consistent with ADR-0022) - Return
X-Cache: HIT/MISSheaders for debugging - Follow existing Go handler patterns from
pkg/server/server.go
Response Schema
{
"version": "1.4.0",
"commit_short": "a3f7b2c1",
"build_date": "2026-05-04T08:00:00Z",
"uptime_seconds": 1234,
"cache_enabled": true,
"healthz_status": "healthy"
}
Rationale
- Performance: Single HTTP request instead of 3-4 separate calls
- Consistency: All data reflects the same moment in time
- Caching: Leverages existing cache infrastructure (ADR-0022) with predictable key pattern
- API Design: Clean, RESTful endpoint with single responsibility
- Maintainability: Clear separation of concerns - info aggregation is a distinct use case
- Backward Compatibility: Existing endpoints remain unchanged
- Frontend Simplicity: Reduces complexity and improves UX
Cache Strategy
Following ADR-0022 pattern:
- Cache key:
info:json(consistent withversion:formatpattern) - TTL:
config.cache.default_ttl_seconds(default 300 seconds) - Cache service:
pkg/cache/cache.goInMemoryService - Headers:
X-Cache: HITorX-Cache: MISS
This allows the endpoint to be fast even under load, while maintaining data freshness.
Consequences
Positive
- Improved frontend performance: Single request instead of multiple
- Better UX: Faster page loads, simpler loading states
- Consistent data: All fields reflect the same point-in-time
- Cache efficiency: Reuses existing cache infrastructure
- Clean separation: Info endpoint handles aggregation, source endpoints unchanged
- Easy to test: Single endpoint with predictable response
Negative
- Data duplication: Some fields appear in multiple endpoints
- Maintenance burden: If source data changes, endpoint must be updated
- New endpoint: Increases API surface area (though minimal)
Mitigation
- Data duplication is acceptable - it's read-only system info
- Source the data from the same packages/functions used by other endpoints
- The new endpoint has a clear, focused purpose
Links
- ADR-0002: Chi Router - Routing foundation
- ADR-0022: Rate Limiting Cache Strategy - Cache pattern reference
- pkg/server/server.go - Handler patterns
- pkg/cache/cache.go - Cache service
- pkg/version/version.go - Version data source