Files
dance-lessons-coach/adr/0026-composite-info-endpoint.md
Gabriel Radureau 8d93050636
All checks were successful
CI/CD Pipeline / Build Docker Cache (push) Successful in 7s
CI/CD Pipeline / CI Pipeline (push) Successful in 4m57s
CI/CD Pipeline / Trigger Docker Push (push) Successful in 6s
feat(server): add go_version to /api/info response (#54)
Co-authored-by: Gabriel Radureau <arcodange@gmail.com>
Co-committed-by: Gabriel Radureau <arcodange@gmail.com>
2026-05-05 10:18:30 +02:00

7.7 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:

  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

{
  "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