From 7d9eafbb43460304f97cac8a2d19444a2d94077d Mon Sep 17 00:00:00 2001 From: rdev-worker Date: Tue, 3 Feb 2026 03:04:46 +0000 Subject: [PATCH] build: /design-feature add-hello-endpoint --- .sdlc/features/add-hello-endpoint/design.md | 201 ++++++++++++++++++ .../features/add-hello-endpoint/feature.json | 4 + 2 files changed, 205 insertions(+) create mode 100644 .sdlc/features/add-hello-endpoint/design.md diff --git a/.sdlc/features/add-hello-endpoint/design.md b/.sdlc/features/add-hello-endpoint/design.md new file mode 100644 index 0000000..f8df02a --- /dev/null +++ b/.sdlc/features/add-hello-endpoint/design.md @@ -0,0 +1,201 @@ +# Design: Add /hello Endpoint to API Service + +## Architecture Approach + +This is a minimal addition that follows existing patterns exactly. No new architectural components are introduced. + +**Layers changed:** +- `services/api/internal/api/handlers/` - New `hello.go` handler file +- `services/api/internal/api/routes.go` - Route registration +- `services/api/internal/api/spec.go` - OpenAPI documentation + +**Approach:** Create a standalone handler struct with a single `Say` method that returns a greeting. The handler follows the error-returning pattern used by `Example` handler (not the direct `http.HandlerFunc` pattern used by `Health`), allowing for consistent error handling if we add complexity later. + +## Data Model Changes + +**No database or schema changes required.** + +Response structure (uses existing envelope): +```go +// HelloResponse is the data returned by GET /api/v1/hello +type HelloResponse struct { + Message string `json:"message"` +} +``` + +Envelope output: +```json +{ + "data": { + "message": "Hello, World!" + }, + "meta": { + "request_id": "abc123", + "timestamp": "2026-02-03T12:00:00Z" + } +} +``` + +## API Changes + +### New Endpoint + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| GET | `/api/v1/hello` | None | Returns greeting message | + +### Request + +No request body or parameters. + +### Response + +**Success (200 OK):** +```json +{ + "data": { + "message": "Hello, World!" + }, + "meta": { + "request_id": "...", + "timestamp": "..." + } +} +``` + +## Component Diagram + +``` +┌──────────────────────────────────────────────────────────────┐ +│ HTTP Request │ +│ GET /api/v1/hello │ +└──────────────────────────────────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────────────┐ +│ Chi Router (routes.go) │ +│ │ +│ r.Get("/hello", app.Wrap(helloHandler.Say)) │ +└──────────────────────────────────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────────────┐ +│ Middleware Stack │ +│ [RequestID] → [CORS] → [Recovery] → [Logger] │ +│ (Already configured in app.App) │ +└──────────────────────────────────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────────────┐ +│ app.Wrap() │ +│ Converts error-returning handler to http.HandlerFunc │ +│ Maps HTTPError → status code, other errors → 500 │ +└──────────────────────────────────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────────────┐ +│ handlers/hello.go │ +│ │ +│ func (h *Hello) Say(w, r) error { │ +│ httpresponse.OK(w, r, HelloResponse{ │ +│ Message: "Hello, World!", │ +│ }) │ +│ return nil │ +│ } │ +└──────────────────────────────────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────────────┐ +│ httpresponse.OK() │ +│ Wraps data in {data, meta} envelope │ +│ Adds request_id and timestamp to meta │ +│ Sets Content-Type: application/json │ +└──────────────────────────────────────────────────────────────┘ + │ + ▼ +┌──────────────────────────────────────────────────────────────┐ +│ HTTP Response │ +│ 200 OK │ +│ {"data": {"message": "Hello, World!"}, "meta": ...} │ +└──────────────────────────────────────────────────────────────┘ +``` + +## Error Handling Strategy + +This endpoint has minimal failure modes: + +| Scenario | Handling | Response | +|----------|----------|----------| +| Normal request | Return greeting | 200 with `{data, meta}` | +| Middleware panic | Recovery middleware catches | 500 with generic error | +| JSON encoding failure | Caught by `httpresponse.OK` | 500 internal error | + +Since this handler: +- Takes no input (no validation errors possible) +- Makes no external calls (no timeout/network errors) +- Has no authorization (no auth errors possible) + +The only realistic failure mode is a panic in the middleware stack, which is already handled by the Recovery middleware. + +**Decision:** Use error-returning handler pattern (`app.Wrap`) for consistency with other handlers, even though this specific handler always returns `nil`. This makes future extension easier if we add features like parameterized greetings. + +## Security Considerations + +| Concern | Mitigation | +|---------|------------| +| **Authentication** | Endpoint is intentionally public (no auth required) | +| **Authorization** | No protected resources accessed | +| **Input validation** | No input accepted (GET with no params/body) | +| **Data exposure** | Only static string returned, no sensitive data | +| **Rate limiting** | Uses global rate limiting if configured (out of scope for this feature) | +| **CORS** | Uses existing CORS middleware configuration | + +**Risk assessment:** Low risk. This is a read-only endpoint returning static content with no user input and no sensitive data. + +## Performance Considerations + +| Aspect | Analysis | +|--------|----------| +| **Expected load** | Low - primarily used for connectivity checks | +| **Response time** | Sub-millisecond (no I/O, no computation) | +| **Memory** | Negligible - single small response struct | +| **Caching** | Not needed - response is static and fast to generate | +| **Connection pooling** | N/A - no database or external calls | + +**Optimization:** None required. The endpoint is already optimal by design. + +## Migration / Rollout Plan + +**Zero-downtime deployment:** This is a purely additive change. + +1. **No migration needed** - No database changes +2. **No feature flags needed** - Endpoint is stateless and low-risk +3. **No backwards compatibility concerns** - New endpoint, no existing consumers + +**Rollout steps:** +1. Deploy code changes (handler, routes, spec) +2. Verify endpoint responds at `/api/v1/hello` +3. Verify OpenAPI spec includes new endpoint at `/docs` + +**Rollback:** Standard deployment rollback if issues arise (no data cleanup needed). + +## File Changes Summary + +| File | Change Type | Description | +|------|-------------|-------------| +| `services/api/internal/api/handlers/hello.go` | New | Handler struct and Say method | +| `services/api/internal/api/handlers/hello_test.go` | New | Unit tests for handler | +| `services/api/internal/api/routes.go` | Modify | Register GET /api/v1/hello route | +| `services/api/internal/api/spec.go` | Modify | Add Hello tag and /hello path | + +## Implementation Notes + +1. **Handler location:** Create `handlers/hello.go` alongside existing handlers rather than adding to `health.go` - the spec recommends a dedicated "Hello" tag to distinguish from infrastructure health checks. + +2. **Response type:** Define `HelloResponse` in the handler file (not a separate types file) since it's only used by this handler. + +3. **Route registration:** Add `helloHandler` initialization in `RegisterRoutes` and register route in the public routes section (outside the auth group). + +4. **OpenAPI tag:** Add `"Hello"` tag with description `"Simple greeting endpoint"` to differentiate from Health endpoints. + +5. **Test pattern:** Follow `example_test.go` pattern with table-driven tests, though a single happy-path test is sufficient for this simple endpoint. diff --git a/.sdlc/features/add-hello-endpoint/feature.json b/.sdlc/features/add-hello-endpoint/feature.json index 108ddaf..a2ea613 100644 --- a/.sdlc/features/add-hello-endpoint/feature.json +++ b/.sdlc/features/add-hello-endpoint/feature.json @@ -7,6 +7,10 @@ "spec": { "status": "approved", "approvedAt": "2026-02-03T03:00:00Z" + }, + "design": { + "status": "draft", + "createdAt": "2026-02-03T03:02:00Z" } } }