Skip to content

API Patterns

Norm Brandinger edited this page Nov 20, 2025 · 1 revision

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
  2. Two Implementation Patterns
  3. Architecture
  4. Synchronization Strategy
  5. Developer Workflows
  6. Technical Details
  7. Testing Strategy
  8. CI/CD Integration
  9. Troubleshooting
  10. 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:

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:

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)

# 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 - 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

# 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)

# 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

# Step 1: Add endpoint to code-first implementation
vim reference-apps/fastapi/app/routers/my_feature.py
# my_feature.py
@router.get("/my-feature/data")
async def get_my_feature_data():
    return {"data": "example"}
# 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

# Step 1: Design API in shared spec
vim reference-apps/shared/openapi.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
# 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:

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

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:

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:

# 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

# 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

# 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

#!/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:

cd reference-apps/fastapi
pytest tests/ -v

API-First:

cd reference-apps/fastapi-api-first
pytest tests/ -v

Purpose: Test implementation-specific logic.

Layer 2: Shared Integration Tests

cd reference-apps/shared/test-suite
pytest -v

Purpose: Ensure both implementations behave identically.

Example:

# 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

# 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

# .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

# .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:

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

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

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

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

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

# 1. Update shared spec
vim reference-apps/shared/openapi.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
# 2. Regenerate API-first
make regenerate

# 3. Implement in API-first
vim reference-apps/fastapi-api-first/custom/implementations.py
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
    }
# 4. Update code-first to match
vim reference-apps/fastapi/app/routers/health.py
@router.get("/redis-cluster")
async def redis_cluster_health():
    """Check Redis cluster health"""
    # Same implementation as API-first
    ...
# 5. Add shared test
vim reference-apps/shared/test-suite/test_health.py
@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
# 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.

# 1. Update shared spec
vim reference-apps/shared/openapi.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"
# 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
# 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
    }
# 4. Update shared tests
vim reference-apps/shared/test-suite/test_health.py
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"))
# 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 or check the individual README files in each implementation directory.

Last Updated: 2025-10-27 | Maintained By: Development Team

Clone this wiki locally