Skip to content

API Contract Strategy

Phase: 1 Architecture Plan Status: pre-implementation contract

Contract Source

The current public contract is the legacy /api/... behavior served by NestJS. Go services may expose cleaner internal /v1/... routes, but the frontend-facing contract stays /api/... through api-gateway until an explicit frontend migration is approved.

Contract Artifacts

Required before implementing a migrated route:

  1. Legacy OpenAPI or route snapshot.
  2. Request examples from current frontend/runtime path.
  3. Response examples for success, validation error, auth error, tenant error, and not found where applicable.
  4. Go service OpenAPI for the native route.
  5. Gateway adapter mapping from legacy route to native route.
  6. Parity test cases.

Compatibility Surface

Must preserve:

  • /api prefix.
  • /api/v1 compatibility rewrite where clients still use it.
  • Bearer token behavior.
  • hoctapaz.accessToken cookie compatibility at gateway.
  • X-Organization-Id.
  • X-Request-Id and X-Correlation-Id.
  • legacy response envelopes.
  • legacy validation and conflict semantics for high-risk flows.
  • SSE event shape for import and AI classification streams.

Error Mapping

Gateway adapters must map service errors to legacy-compatible HTTP status and body shapes.

Minimum canonical internal error fields:

json
{
  "code": "QUESTION_VERSION_CONFLICT",
  "message": "serverVersion does not match clientVersion",
  "details": {},
  "requestId": "..."
}

Gateway may translate this into legacy shape for /api/.... Native /v1/... can keep canonical errors as long as they are documented.

SSE Contract

SSE routes are not normal JSON endpoints. Gateway must:

  • stream incrementally
  • avoid buffering full response
  • forward heartbeat events
  • preserve event names and data payload fields
  • close on client disconnect
  • propagate correlation ID into worker logs where possible

Initial SSE routes:

  • /api/exam-import/algorithm-jobs/events
  • /api/exam-import/algorithm-jobs/:id/events
  • /api/questions/ai-classify/jobs/:id/events
  • attempt/exam event streams if used by frontend

Gateway Route States

Each route in the gateway route table has one state:

StateMeaning
legacy_proxyForward to NestJS unchanged.
shadow_readServe legacy response, also call Go service for diff only.
native_readServe Go response for read route, fallback disabled unless route-level rollback is triggered.
native_write_shadow_validateWrite remains legacy or one side authoritative; compare resulting state.
native_writeGo service is authoritative.
removedOnly after frontend and ops confirm no callers.

No route can jump directly from legacy_proxy to native_write without documented parity evidence.

When the native path differs from the legacy path, the gateway route table uses target_prefix to adapt the public /api/... path to the internal /v1/... service path. This keeps frontend edits out of scope during migration.

Route table entries may also set methods, exact, and suffix_segments for read-only cutovers inside a shared prefix. For example, GET /api/questions can be shadowed as an exact route while GET /api/questions/:id can be shadowed with suffix_segments: 1; POST /api/questions, PATCH /api/questions/:id, /api/questions/ai-classify/*, and other nested subroutes continue through the broader legacy /api/questions prefix.

Contract Test Levels

  • Unit: handler and service behavior.
  • Adapter: gateway request/response mapping.
  • Parity: same seeded input against legacy and Go route.
  • Browser/runtime: real current route where frontend behavior is critical, especially import editor saves, question types, and attempts.

Frontend Rule

Do not edit apps/web during initial backend rewrite. If a contract cannot be preserved without frontend change, stop and document the incompatibility in the risk register.

Go-platform documentation is generated from repository Markdown.