# API Implementation Patterns - Complete Guide **Last Updated:** 2025-10-27 **Status:** ✅ Active - Both patterns implemented with platform-agnostic sync validation **Enforcement:** Pre-commit hooks (PRIMARY) + Makefile + Optional CI/CD --- ## Table of Contents 1. [Overview](#overview) 2. [Two Implementation Patterns](#two-implementation-patterns) 3. [Architecture](#architecture) 4. [Synchronization Strategy](#synchronization-strategy) 5. [Developer Workflows](#developer-workflows) 6. [Technical Details](#technical-details) 7. [Testing Strategy](#testing-strategy) 8. [CI/CD Integration](#cicd-integration) 9. [Troubleshooting](#troubleshooting) 10. [Examples](#examples) --- ## Overview This reference implementation demonstrates **two real-world API development patterns**: 1. **Code-First Pattern** - Implementation drives documentation 2. **API-First Pattern** - Contract drives implementation Both patterns are **kept in perfect synchronization** through automated validation, ensuring developers can learn from accurate, working examples of both approaches. ### Why Two Patterns? Different projects need different approaches: | Pattern | Best For | Common In | |---------|----------|-----------| | **Code-First** | Rapid prototyping, startups, internal tools | FastAPI projects, Django REST, small teams | | **API-First** | Microservices, public APIs, large teams | Enterprise, banks, public APIs, SaaS platforms | **This project shows both so you can choose the right pattern for your needs.** --- ## Two Implementation Patterns ### Pattern 1: Code-First (Current `fastapi/`) **Philosophy:** Write code, documentation follows automatically. ``` Code → Auto-Generated Docs → Runtime OpenAPI ``` **Directory Structure:** ``` reference-apps/fastapi/ ├── app/ │ ├── main.py # FastAPI app definition │ ├── routers/ # Endpoint implementations │ │ ├── health.py │ │ ├── vault_demo.py │ │ └── redis_cluster.py │ └── models/ # Pydantic models ├── Dockerfile └── README.md ``` **Access Points:** - Swagger UI: http://localhost:8000/docs - ReDoc: http://localhost:8000/redoc - OpenAPI JSON: http://localhost:8000/openapi.json **When to Use:** - ✅ Rapid prototyping - ✅ Small teams (1-3 developers) - ✅ Internal tools - ✅ Learning FastAPI - ✅ Iterating on design quickly **Pros:** - Fast initial development - Less tooling required - Direct code changes - Hot reload in development **Cons:** - Documentation can drift - Harder to coordinate multiple teams - No client SDK generation before implementation - Contract changes are implicit --- ### Pattern 2: API-First (New `fastapi-api-first/`) **Philosophy:** Design contract first, generate code from specification. ``` OpenAPI Spec → Code Generation → Implementation → Validation ``` **Directory Structure:** ``` reference-apps/ ├── shared/ │ ├── openapi.yaml # ⭐ SINGLE SOURCE OF TRUTH │ └── test-suite/ # Shared tests for both │ ├── test_health.py │ ├── test_vault.py │ └── test_redis.py ├── fastapi-api-first/ │ ├── generated/ # Auto-generated (don't edit) │ │ ├── main.py │ │ ├── models/ │ │ └── apis/ │ ├── custom/ # ✏️ Your business logic │ │ ├── implementations.py │ │ └── services/ │ ├── codegen.sh # Regenerate from spec │ └── README.md └── scripts/ ├── validate-sync.sh # Core sync validation ├── sync-report.sh # Detailed diff reports ├── regenerate-api-first.sh # Regenerate API-first ├── install-hooks.sh # Install pre-commit hooks └── hooks/ └── pre-commit # PRIMARY enforcement layer ``` **Access Points:** - Swagger UI: http://localhost:8001/docs - ReDoc: http://localhost:8001/redoc - OpenAPI JSON: http://localhost:8001/openapi.json **When to Use:** - ✅ Microservices architecture - ✅ Multiple teams (frontend/backend) - ✅ Public/external APIs - ✅ Client SDK generation needed - ✅ Contract testing required - ✅ Enterprise environments **Pros:** - Contract is guaranteed - Generate clients in any language - Teams can work in parallel - Design review before implementation - Breaking changes are explicit **Cons:** - More initial setup - Requires code generation tooling - Slower iteration (spec → generate → implement) - Learning curve for tooling --- ## Architecture ### High-Level Overview ``` ┌─────────────────────────────────────────────────────────────┐ │ Shared OpenAPI Spec │ │ (shared/openapi.yaml) │ │ SINGLE SOURCE OF TRUTH │ └──────────────────────┬──────────────────────────────────────┘ │ ┌─────────────┴─────────────┐ ↓ ↓ ┌────────────────────┐ ┌────────────────────┐ │ Code-First │ │ API-First │ │ Implementation │ │ Implementation │ │ │ │ │ │ Manual Code │ │ Generated Code │ │ (fastapi/) │ │ (fastapi-api- │ │ │ │ first/generated/)│ │ Port: 8000 │ │ Port: 8001 │ └────────┬───────────┘ └──────┬─────────────┘ │ │ └──────────┬──────────────┘ ↓ ┌────────────────────┐ │ Shared Test Suite │ │ (Both must pass) │ └────────────────────┘ ↓ ┌────────────────────┐ │ CI/CD Validation │ │ (Enforces sync) │ └────────────────────┘ ``` ### Synchronization Mechanism Both implementations are validated against the **shared OpenAPI specification**: ``` 1. Extract OpenAPI from code-first (runtime) 2. Compare with shared/openapi.yaml 3. Run shared tests against both implementations 4. If any differences → CI FAILS ❌ 5. If all match → CI PASSES ✅ ``` **This ensures both implementations are always identical in behavior.** --- ## Synchronization Strategy ### The Problem Multiple implementations **will** drift apart without enforcement: ``` Week 1: Both match ✅ Week 2: Added endpoint to code-first only ❌ Week 3: Changed response in API-first only ❌ Week 4: Completely different APIs ❌❌❌ ``` ### The Solution: Platform-Agnostic Validation **Four-Layer Protection Strategy:** #### Layer 1: Pre-commit Hooks (PRIMARY Enforcement - Local) ```bash # scripts/hooks/pre-commit # Runs BEFORE every commit - catches issues locally BEFORE push # This is the PRIMARY enforcement mechanism (CI/CD is just a safety net) if git diff --cached --name-only | grep -q "openapi.yaml"; then # Validate OpenAPI YAML syntax yq eval . "$SHARED_SPEC" >/dev/null 2>&1 fi if git diff --cached --name-only | grep -qE "fastapi.*\.py"; then # Check API synchronization if both implementations running if both_apis_running; then make sync-check || exit 1 fi fi # Install with: make install-hooks # Bypass with: git commit --no-verify (discouraged) ``` #### Layer 2: Makefile (Standard Interface) ```makefile # Makefile - Platform-agnostic standard interface # Works with ANY CI/CD system (GitHub Actions, GitLab CI, Jenkins, etc.) validate: ## Run all validation checks (use in CI/CD) @$(MAKE) validate-spec @$(MAKE) sync-check @$(MAKE) test sync-check: ## Check if both implementations match OpenAPI spec @make sync-check sync-report: ## Generate detailed synchronization report @./scripts/sync-report.sh regenerate: ## Regenerate API-first from shared spec @make regenerate install-hooks: ## Install pre-commit hooks @./scripts/install-hooks.sh # Any CI/CD system invokes: make validate ``` #### Layer 3: Portable Bash Scripts ```bash # scripts/validate-sync.sh - Core validation logic # Platform-agnostic (works on any Unix-like system) # Google-style documented functions # 1. Extract specs from running APIs extract_spec "$CODE_FIRST_URL" "code-first.yaml" extract_spec "$API_FIRST_URL" "api-first.yaml" # 2. Normalize specs (remove implementation-specific fields) normalize_spec "code-first.yaml" "code-first-normalized.yaml" # 3. Compare normalized specs compare_specs "code-first-normalized.yaml" "shared/openapi.yaml" # Exit codes: 0=synced, 1=out of sync, 2=APIs unreachable ``` #### Layer 4: Optional CI/CD Adapters (Any Platform) ```yaml # Works with ANY CI/CD system - just invoke Makefile targets # GitHub Actions: - run: make validate # GitLab CI: script: - make validate # Jenkins: sh 'make validate' # Platform-agnostic design - NO GitHub dependency ``` --- ## Developer Workflows ### Workflow 1: Adding a New Endpoint (Code-First Approach) **Use when:** Prototyping, exploring design ```bash # Step 1: Add endpoint to code-first implementation vim reference-apps/fastapi/app/routers/my_feature.py ``` ```python # my_feature.py @router.get("/my-feature/data") async def get_my_feature_data(): return {"data": "example"} ``` ```bash # Step 2: Test it works curl http://localhost:8000/my-feature/data # Step 3: Extract new OpenAPI spec make extract-openapi # Step 4: Update shared spec # Manually review and update reference-apps/shared/openapi.yaml # Step 5: Regenerate API-first implementation make regenerate # Step 6: Implement business logic in API-first vim reference-apps/fastapi-api-first/custom/implementations.py # Step 7: Add shared tests vim reference-apps/shared/test-suite/test_my_feature.py # Step 8: Validate synchronization make sync-check # Step 9: Run all tests make test # Step 10: Commit changes git add . git commit -m "Add new feature endpoint" # Pre-commit hook validates sync automatically ✓ ``` **Expected Time:** 15-30 minutes --- ### Workflow 2: Adding a New Endpoint (API-First Approach) **Use when:** Coordinating with other teams, public API ```bash # Step 1: Design API in shared spec vim reference-apps/shared/openapi.yaml ``` ```yaml # Add to openapi.yaml paths: /my-feature/data: get: summary: Get my feature data responses: '200': description: Success content: application/json: schema: type: object properties: data: type: string ``` ```bash # Step 2: Validate spec syntax spectral lint reference-apps/shared/openapi.yaml # Step 3: Regenerate API-first implementation make regenerate # Step 4: Implement business logic vim reference-apps/fastapi-api-first/custom/implementations.py # Step 5: Test API-first works curl http://localhost:8001/my-feature/data # Step 6: Update code-first to match vim reference-apps/fastapi/app/routers/my_feature.py # Implement identical endpoint # Step 7: Add shared tests vim reference-apps/shared/test-suite/test_my_feature.py # Step 8: Validate synchronization make sync-check # Step 9: Run all tests cd reference-apps/shared/test-suite pytest -v # Step 10: Commit changes git add . git commit -m "Add new feature endpoint" ``` **Expected Time:** 20-40 minutes --- ### Workflow 3: Modifying an Existing Endpoint **Always start with shared spec:** ```bash # Step 1: Update shared/openapi.yaml vim reference-apps/shared/openapi.yaml # Change response schema, add field, etc. # Step 2: Regenerate API-first make regenerate # Step 3: Update business logic in API-first vim reference-apps/fastapi-api-first/custom/implementations.py # Step 4: Update code-first manually vim reference-apps/fastapi/app/routers/existing_feature.py # Step 5: Update shared tests vim reference-apps/shared/test-suite/test_existing_feature.py # Step 6: Validate sync make sync-check # Step 7: Commit git add . git commit -m "Update existing endpoint response" ``` --- ### Workflow 4: Checking Synchronization Status **Quick check:** ```bash make sync-check ``` **Output:** ``` ✓ Starting code-first API... ✓ Extracting OpenAPI spec... ✓ Comparing specifications... ✓ Running shared tests (code-first)... ✓ Running shared tests (API-first)... ✓ Contract testing (code-first)... ✓ Contract testing (API-first)... ✅ APIs are synchronized - all validations passed ``` **Detailed report:** ```bash make sync-report ``` **Output:** ``` # API Synchronization Report ## Status: ❌ OUT OF SYNC ## Differences Found: ### Missing in Code-First: - POST /my-feature/data (defined in shared spec) ### Extra in Code-First: - GET /debug/internal (not in shared spec) ### Schema Differences: - /health/all response: code-first has extra field "uptime" ## Action Required: 1. Add POST /my-feature/data to code-first, OR 2. Remove from shared spec 3. Remove GET /debug/internal from code-first, OR 4. Add to shared spec 5. Align /health/all response schemas Follow the action steps above to fix synchronization. ``` --- ### Workflow 5: Emergency Sync Fix **When CI/pre-commit hooks are failing due to sync issues:** ```bash # Step 1: Check current status make status # Step 2: Generate detailed report make sync-report > /tmp/sync-diff.txt cat /tmp/sync-diff.txt # Step 3: Option A - Make code-first authoritative # Extract spec from code-first make extract-openapi # Manually update shared/openapi.yaml based on extracted spec # Regenerate API-first from updated shared spec make regenerate # Step 4: Option B - Make shared spec authoritative # Regenerate API-first from shared spec make regenerate # Manually update code-first routers to match shared spec # Step 5: Validate synchronization make sync-check # Step 6: Run all tests make test # Step 7: Commit (pre-commit hook will validate automatically) git add . git commit -m "Fix API synchronization" ``` --- ## Technical Details ### OpenAPI Specification Structure ```yaml # shared/openapi.yaml openapi: 3.0.0 info: title: DevStack Core Reference API version: 1.0.0 description: | Reference implementation demonstrating infrastructure integration patterns. Features: - Health monitoring for all services - Vault secret management examples - Database connectivity (PostgreSQL, MySQL, MongoDB) - Redis cluster operations - RabbitMQ messaging patterns ⚠️ This is a REFERENCE implementation for learning, not production code. servers: - url: http://localhost:8000 description: Code-First Implementation - url: http://localhost:8001 description: API-First Implementation security: - ApiKeyAuth: [] components: securitySchemes: ApiKeyAuth: type: apiKey in: header name: X-API-Key schemas: HealthStatus: type: object required: - status - services properties: status: type: string enum: [healthy, degraded, unhealthy] services: type: object additionalProperties: $ref: '#/components/schemas/ServiceHealth' ServiceHealth: type: object required: - status properties: status: type: string enum: [healthy, unhealthy] details: type: object ErrorResponse: type: object required: - error - message properties: error: type: string message: type: string details: type: object status_code: type: integer paths: /health/all: get: summary: Check health of all infrastructure services description: | Returns aggregate health status for: - Vault - PostgreSQL - MySQL - MongoDB - Redis (cluster) - RabbitMQ tags: - Health responses: '200': description: Health status retrieved content: application/json: schema: $ref: '#/components/schemas/HealthStatus' examples: all_healthy: summary: All services healthy value: status: healthy services: vault: status: healthy details: initialized: true sealed: false postgres: status: healthy redis: status: healthy details: cluster_state: ok nodes: 3 '503': description: One or more services unhealthy content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' ``` ### Code Generation Configuration ```yaml # reference-apps/fastapi-api-first/openapi-generator-config.yaml generatorName: python-fastapi inputSpec: ../shared/openapi.yaml outputDir: ./generated additionalProperties: packageName: app projectName: devstack-core-api-first packageVersion: 1.0.0 globalProperties: models: true apis: true supportingFiles: true templateDir: ./templates # Custom templates if needed ``` ### Validation Script Architecture ```bash #!/bin/bash # scripts/validate-sync.sh set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" # Colors GREEN='\033[0;32m' RED='\033[0;31m' YELLOW='\033[1;33m' NC='\033[0m' info() { echo -e "${GREEN}✓${NC} $1"; } error() { echo -e "${RED}✗${NC} $1"; } warn() { echo -e "${YELLOW}⚠${NC} $1"; } # Step 1: Start services info "Starting services..." cd "$PROJECT_ROOT" ./devstack.sh start sleep 30 # Step 2: Wait for both APIs info "Waiting for APIs to be ready..." for i in {1..30}; do if curl -sf http://localhost:8000/health >/dev/null && \ curl -sf http://localhost:8001/health >/dev/null; then break fi sleep 2 done # Step 3: Extract OpenAPI from code-first info "Extracting OpenAPI from code-first..." curl -sf http://localhost:8000/openapi.json > /tmp/code-first-openapi.json # Step 4: Compare with shared spec info "Comparing specifications..." docker run --rm \ -v "$PROJECT_ROOT:/work" \ -v "/tmp:/tmp" \ openapitools/openapi-diff:latest \ /work/reference-apps/shared/openapi.yaml \ /tmp/code-first-openapi.json \ --fail-on-incompatible if [ $? -ne 0 ]; then error "SPEC MISMATCH: Code-first doesn't match shared spec" echo "Run 'make sync-report' for details" exit 1 fi # Step 5: Run shared test suite (code-first) info "Running shared tests (code-first)..." cd "$PROJECT_ROOT/reference-apps/shared/test-suite" pytest --api-url=http://localhost:8000 -v --tb=short # Step 6: Run shared test suite (API-first) info "Running shared tests (API-first)..." pytest --api-url=http://localhost:8001 -v --tb=short # Step 7: Contract testing (code-first) info "Contract testing (code-first)..." dredd "$PROJECT_ROOT/reference-apps/shared/openapi.yaml" \ http://localhost:8000 # Step 8: Contract testing (API-first) info "Contract testing (API-first)..." dredd "$PROJECT_ROOT/reference-apps/shared/openapi.yaml" \ http://localhost:8001 info "APIs are synchronized - all validations passed" ``` --- ## Testing Strategy ### Three Layers of Testing #### Layer 1: Unit Tests (Per-Implementation) **Code-First:** ```bash cd reference-apps/fastapi pytest tests/ -v ``` **API-First:** ```bash cd reference-apps/fastapi-api-first pytest tests/ -v ``` **Purpose:** Test implementation-specific logic. #### Layer 2: Shared Integration Tests ```bash cd reference-apps/shared/test-suite pytest -v ``` **Purpose:** Ensure both implementations behave identically. **Example:** ```python # shared/test-suite/test_redis_cluster.py @pytest.mark.parametrize("api_url", [ "http://localhost:8000", # Code-first "http://localhost:8001", # API-first ]) class TestRedisCluster: async def test_cluster_nodes(self, api_url): async with httpx.AsyncClient() as client: response = await client.get(f"{api_url}/redis/cluster/nodes") assert response.status_code == 200 data = response.json() assert "nodes" in data assert data["total_nodes"] == 3 ``` #### Layer 3: Contract Testing ```bash # Validates API matches OpenAPI spec exactly dredd shared/openapi.yaml http://localhost:8000 dredd shared/openapi.yaml http://localhost:8001 ``` **Purpose:** Ensure implementation matches contract. ### Test Coverage Requirements Both implementations must maintain: - ✅ **80%+ code coverage** (unit tests) - ✅ **100% endpoint coverage** (shared tests) - ✅ **100% schema validation** (contract tests) --- ## CI/CD Integration ### GitHub Actions Workflow ```yaml # .github/workflows/api-sync-validation.yml name: API Synchronization Validation on: pull_request: paths: - 'reference-apps/**' - 'scripts/**' push: branches: [main, develop] jobs: validate-api-sync: name: Validate Both API Implementations Match runs-on: ubuntu-latest timeout-minutes: 30 steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Start infrastructure run: | ./devstack.sh start sleep 30 - name: Wait for services run: | timeout 300 bash -c 'until curl -sf http://localhost:8000/health && curl -sf http://localhost:8001/health; do sleep 5; done' - name: Extract OpenAPI from code-first run: | curl http://localhost:8000/openapi.json > /tmp/code-first.json - name: Install validation tools run: | npm install -g dredd @stoplight/spectral-cli pip install pytest pytest-asyncio httpx - name: Validate shared spec syntax run: | spectral lint reference-apps/shared/openapi.yaml --fail-severity=warn - name: Compare specifications run: | docker run --rm \ -v "${PWD}:/work" \ -v "/tmp:/tmp" \ openapitools/openapi-diff:latest \ /work/reference-apps/shared/openapi.yaml \ /tmp/code-first.json \ --fail-on-incompatible - name: Run shared test suite (both implementations) run: | cd reference-apps/shared/test-suite pytest -v --tb=short - name: Contract test code-first run: | dredd reference-apps/shared/openapi.yaml http://localhost:8000 - name: Contract test API-first run: | dredd reference-apps/shared/openapi.yaml http://localhost:8001 - name: Generate sync report on failure if: failure() run: | ./scripts/sync-report.sh > sync-report.md cat sync-report.md - name: Upload sync report if: failure() uses: actions/upload-artifact@v3 with: name: sync-report path: sync-report.md - name: Comment on PR if: failure() && github.event_name == 'pull_request' uses: actions/github-script@v7 with: script: | const fs = require('fs'); const report = fs.readFileSync('sync-report.md', 'utf8'); github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: `## ❌ API Synchronization Failed\n\n${report}` }); ``` ### Pre-Commit Hook ```bash # .git/hooks/pre-commit #!/bin/bash # Only validate if API files changed if git diff --cached --name-only | grep -q "reference-apps/"; then echo "🔍 Validating API synchronization..." make sync-check if [ $? -ne 0 ]; then echo "" echo "❌ Pre-commit validation failed" echo "Fix synchronization issues before committing" echo "Run 'make sync-report' for details" exit 1 fi echo "✅ API synchronization validated" fi ``` **Installation:** ```bash # Install pre-commit hooks (creates symlink for easy updates) make install-hooks # Hooks will now run automatically on every commit # Bypass with: git commit --no-verify (discouraged) # Test hook manually .git/hooks/pre-commit ``` --- ## Troubleshooting ### Problem: "SPEC MISMATCH" error **Symptom:** ``` ✗ SPEC MISMATCH: Code-first doesn't match shared spec Run 'make sync-report' for details ``` **Solution:** ```bash # Step 1: See what's different make sync-report # Step 2: Decide which is authoritative # Option A: Code-first is correct make extract-openapi # Manually update shared/openapi.yaml make regenerate # Option B: Shared spec is correct make regenerate # Manually update code-first routers to match # Step 3: Validate make sync-check ``` --- ### Problem: Shared tests fail on one implementation **Symptom:** ``` test_health_all[http://localhost:8000] PASSED test_health_all[http://localhost:8001] FAILED ``` **Solution:** ```bash # Step 1: Check logs for that implementation docker logs dev-reference-api-first # Step 2: Test manually curl http://localhost:8001/health/all # Step 3: Compare responses diff <(curl -s http://localhost:8000/health/all | jq -S .) \ <(curl -s http://localhost:8001/health/all | jq -S .) # Step 4: Fix the failing implementation vim reference-apps/fastapi-api-first/custom/implementations.py # Step 5: Re-test pytest shared/test-suite/test_health.py -v ``` --- ### Problem: Contract test fails **Symptom:** ``` dredd shared/openapi.yaml http://localhost:8000 fail: GET /health/all returns extra field "uptime" ``` **Solution:** ```bash # Step 1: Determine if field should exist # If YES: Update shared spec vim reference-apps/shared/openapi.yaml # Add "uptime" field to schema # If NO: Remove from implementation vim reference-apps/fastapi/app/routers/health.py # Remove "uptime" from response # Step 2: Re-validate make sync-check ``` --- ### Problem: Code generation fails **Symptom:** ``` Error: Unable to generate code from specification ``` **Solution:** ```bash # Step 1: Validate spec syntax spectral lint reference-apps/shared/openapi.yaml # Step 2: Fix any errors in spec vim reference-apps/shared/openapi.yaml # Step 3: Try generation again make regenerate # Step 4: Check generator logs cat reference-apps/fastapi-api-first/generated/.openapi-generator/FILES ``` --- ## Examples ### Example 1: Adding a New Health Check Endpoint **Scenario:** Add `/health/redis-cluster` endpoint to check Redis cluster specifically. **Using API-First:** ```bash # 1. Update shared spec vim reference-apps/shared/openapi.yaml ``` ```yaml # Add to paths: /health/redis-cluster: get: summary: Check Redis cluster health specifically tags: - Health responses: '200': description: Redis cluster health content: application/json: schema: type: object properties: cluster_state: type: string enum: [ok, fail] nodes: type: integer slots_covered: type: integer ``` ```bash # 2. Regenerate API-first make regenerate # 3. Implement in API-first vim reference-apps/fastapi-api-first/custom/implementations.py ``` ```python async def get_redis_cluster_health(): # Implementation creds = await vault_client.get_secret("redis-1") client = redis.Redis(host=settings.REDIS_HOST, password=creds["password"]) info = await client.execute_command("CLUSTER", "INFO") await client.close() return { "cluster_state": "ok" if "cluster_state:ok" in info else "fail", "nodes": 3, "slots_covered": 16384 } ``` ```bash # 4. Update code-first to match vim reference-apps/fastapi/app/routers/health.py ``` ```python @router.get("/redis-cluster") async def redis_cluster_health(): """Check Redis cluster health""" # Same implementation as API-first ... ``` ```bash # 5. Add shared test vim reference-apps/shared/test-suite/test_health.py ``` ```python @pytest.mark.parametrize("api_url", ["http://localhost:8000", "http://localhost:8001"]) async def test_redis_cluster_health(api_url): async with httpx.AsyncClient() as client: response = await client.get(f"{api_url}/health/redis-cluster") assert response.status_code == 200 data = response.json() assert "cluster_state" in data assert data["cluster_state"] in ["ok", "fail"] assert "nodes" in data ``` ```bash # 6. Validate and test make sync-check cd reference-apps/shared/test-suite pytest test_health.py::test_redis_cluster_health -v ``` --- ### Example 2: Changing an Existing Response Schema **Scenario:** Add `timestamp` field to all health check responses. ```bash # 1. Update shared spec vim reference-apps/shared/openapi.yaml ``` ```yaml # Update HealthStatus schema components: schemas: HealthStatus: type: object required: - status - services - timestamp # NEW properties: status: type: string enum: [healthy, degraded, unhealthy] services: type: object timestamp: # NEW type: string format: date-time example: "2025-10-27T12:34:56Z" ``` ```bash # 2. Regenerate API-first make regenerate # 3. Update implementations # API-first: vim reference-apps/fastapi-api-first/custom/implementations.py # Code-first: vim reference-apps/fastapi/app/routers/health.py ``` ```python # Both implementations: from datetime import datetime, timezone @router.get("/all") async def health_all(): # ... existing logic ... return { "status": status, "services": services, "timestamp": datetime.now(timezone.utc).isoformat() # NEW } ``` ```bash # 4. Update shared tests vim reference-apps/shared/test-suite/test_health.py ``` ```python async def test_health_all(api_url): response = await client.get(f"{api_url}/health/all") data = response.json() assert "timestamp" in data # Validate ISO format datetime.fromisoformat(data["timestamp"].replace("Z", "+00:00")) ``` ```bash # 5. Validate make sync-check ``` --- ## Summary This documentation provides: 1. ✅ **Complete understanding** of both patterns 2. ✅ **Step-by-step workflows** for all common tasks 3. ✅ **Technical details** for implementation 4. ✅ **Testing strategy** to ensure quality 5. ✅ **CI/CD integration** for automation 6. ✅ **Troubleshooting guide** for common issues 7. ✅ **Real examples** you can follow ### Key Principles 1. **Shared OpenAPI spec is the single source of truth** 2. **Both implementations must behave identically** 3. **Automated validation prevents drift** 4. **CI/CD enforces synchronization** 5. **Shared tests guarantee compatibility** ### Next Steps - [ ] Read through workflows relevant to your use case - [ ] Try adding a simple endpoint using both patterns - [ ] Run validation scripts to see them in action - [ ] Review CI/CD workflow to understand automation --- **Questions? See [TROUBLESHOOTING](#troubleshooting) or check the individual README files in each implementation directory.** **Last Updated:** 2025-10-27 | **Maintained By:** Development Team