Pular para conteúdo

RFC-012: API Design Standards e Versionamento

Campo Valor
Status Draft
Author Time de Tecnologia
Created 2026-02-05
Updated 2026-02-05
Reunião A ser agendada
Deciders Todo o time técnico

Status

🟡 Draft - RFC em elaboração, aguardando apresentação

Contexto e Problema

REST APIs documentadas em docs/architecture/api-design.md, mas falta:

  • Versionamento strategy formal (URL vs header vs query param)
  • Deprecation policy com timelines claros
  • Breaking changes process definido
  • OpenAPI spec enforcement no CI/CD
  • Backwards compatibility requirements

Por que resolver isso agora?

  • APIs crescendo, necessidade de versionar
  • Breaking changes não têm processo claro
  • Clientes externos começando a usar APIs
  • Falta documentação automática (OpenAPI)
  • Necessidade de deprecar endpoints antigos

Impacto de não resolver

  • Breaking changes quebram clientes
  • APIs crescem desorganizadas
  • Dificuldade em deprecar código legado
  • Documentação desatualizada
  • Clientes frustrados com mudanças inesperadas

Documentação relacionada: - docs/architecture/api-design.md - Design atual REST APIs - docs/api-reference/swagger-ui.md - Swagger UI - docs/api-reference/index.md - Referência APIs - docs/architecture/overview.md - Arquitetura geral

Proposta de Solução

API design standards com URL versioning, deprecation policy de 6 meses, e OpenAPI automático.

Versionamento Strategy

URL Path Versioning (escolhido):

/api/v1/users
/api/v2/users
/api/v3/users

Quando criar nova versão: - ✅ Breaking change em request/response format - ✅ Remover campos obrigatórios - ✅ Mudar tipos de dados - ✅ Mudar comportamento significativo - ❌ Adicionar campos opcionais (backward compatible) - ❌ Adicionar novos endpoints - ❌ Bug fixes

Versão atual: v1

URL Structure

# Recursos
GET    /api/v1/users                    # List
POST   /api/v1/users                    # Create
GET    /api/v1/users/{id}               # Get
PUT    /api/v1/users/{id}               # Update (full)
PATCH  /api/v1/users/{id}               # Update (partial)
DELETE /api/v1/users/{id}               # Delete

# Sub-recursos
GET    /api/v1/users/{id}/orders        # List user orders
POST   /api/v1/users/{id}/orders        # Create order

# Ações (quando REST não se aplica bem)
POST   /api/v1/users/{id}/reset-password
POST   /api/v1/orders/{id}/cancel
POST   /api/v1/orders/{id}/refund

# Queries
GET    /api/v1/users?status=active&role=admin&page=1&page_size=20

Request/Response Standards

Request (POST/PUT/PATCH):

{
  "email": "user@example.com",
  "name": "John Doe",
  "age": 30
}

Response Success (200/201):

{
  "id": 123,
  "email": "user@example.com",
  "name": "John Doe",
  "age": 30,
  "created_at": "2026-01-20T10:00:00Z",
  "updated_at": "2026-01-20T10:00:00Z"
}

Response Error (4xx/5xx):

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid input data",
    "details": [
      {
        "field": "email",
        "message": "Invalid email format",
        "code": "INVALID_FORMAT"
      }
    ],
    "request_id": "550e8400-e29b-41d4-a716-446655440000"
  }
}

Pagination

Request:

GET /api/v1/users?page=2&page_size=20&sort_by=created_at&order=desc

Response:

{
  "data": [...],
  "pagination": {
    "page": 2,
    "page_size": 20,
    "total_items": 150,
    "total_pages": 8,
    "has_next": true,
    "has_prev": true,
    "links": {
      "next": "/api/v1/users?page=3&page_size=20",
      "prev": "/api/v1/users?page=1&page_size=20",
      "first": "/api/v1/users?page=1&page_size=20",
      "last": "/api/v1/users?page=8&page_size=20"
    }
  }
}

OpenAPI Specification

FastAPI (automático):

from fastapi import FastAPI
from pydantic import BaseModel, Field

app = FastAPI(
    title="People API",
    description="API for People management system",
    version="1.0.0",
    docs_url="/api/v1/docs",  # Swagger UI
    redoc_url="/api/v1/redoc",  # ReDoc
    openapi_url="/api/v1/openapi.json"
)

class UserCreate(BaseModel):
    """User creation request."""
    email: str = Field(..., example="user@example.com", description="User email")
    name: str = Field(..., min_length=2, max_length=100, example="John Doe")
    age: int | None = Field(None, ge=0, le=150, example=30)

    model_config = {
        "json_schema_extra": {
            "example": {
                "email": "john@example.com",
                "name": "John Doe",
                "age": 30
            }
        }
    }

@app.post(
    "/api/v1/users",
    response_model=UserResponse,
    status_code=201,
    summary="Create a new user",
    description="Creates a new user with the provided data",
    response_description="The created user",
    tags=["Users"]
)
async def create_user(user: UserCreate):
    """
    Create a new user.

    - **email**: must be valid email format
    - **name**: 2-100 characters
    - **age**: optional, 0-150
    """
    ...

Django REST Framework:

from rest_framework import serializers, viewsets
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi

class UserSerializer(serializers.ModelSerializer):
    """User serializer for API."""

    class Meta:
        model = User
        fields = ['id', 'email', 'name', 'age', 'created_at']
        read_only_fields = ['id', 'created_at']

class UserViewSet(viewsets.ModelViewSet):
    """User management endpoints."""
    queryset = User.objects.all()
    serializer_class = UserSerializer

    @swagger_auto_schema(
        operation_description="Create a new user",
        responses={
            201: UserSerializer,
            400: "Validation error"
        }
    )
    def create(self, request):
        ...

OpenAPI Spec Export:

# FastAPI (built-in)
curl http://localhost:8000/api/v1/openapi.json > openapi.json

# Django REST Framework
python manage.py spectacular --file openapi.json

# CI/CD: auto-publish to docs site
cp openapi.json docs/api-reference/openapi.json

Deprecation Policy

Timeline: 6 meses

Processo:

  1. Announce (T-6 months): Comunicar deprecation
  2. Blog post
  3. Email para clients
  4. Header em responses: X-API-Deprecation: version=v1, sunset=2026-08-01

  5. Warn (T-3 months): Avisos mais agressivos

  6. Warning em response body
  7. Emails semanais para clients usando versão antiga

  8. Sunset (T-0): Desligar versão antiga

  9. Retornar 410 Gone
  10. Redirecionar para documentação de migração

Response durante deprecation:

{
  "data": {...},
  "warnings": [
    {
      "code": "DEPRECATED_API_VERSION",
      "message": "API v1 will be deprecated on 2026-08-01. Please migrate to v2.",
      "migration_guide": "https://docs.seuapp.com/migration/v1-to-v2"
    }
  ]
}

Headers:

X-API-Deprecation: version=v1, sunset=2026-08-01
X-API-Current-Version: v2
Link: <https://docs.seuapp.com/migration/v1-to-v2>; rel="migration-guide"

Breaking Changes

Exemplos de breaking changes: - Remover endpoint - Remover campo de response - Mudar tipo de campo (string → int) - Tornar campo opcional → obrigatório - Mudar formato de data - Mudar status codes

Processo para breaking change:

  1. RFC obrigatória para breaking changes
  2. Nova versão da API (/api/v2/)
  3. Manter versão antiga por 6 meses mínimo
  4. Migration guide detalhado
  5. Deprecation warnings conforme policy

NON-breaking changes (ok fazer em mesma versão): - Adicionar endpoint novo - Adicionar campo opcional em request - Adicionar campo em response - Adicionar valor enum - Bug fixes

Rate Limiting

# Por IP
@limiter.limit("100/hour")

# Por usuário autenticado
@limiter.limit("1000/hour", key_func=lambda: g.user.id)

# Por endpoint crítico
@router.post("/login")
@limiter.limit("5/minute")  # Anti-brute force

# Response headers
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 73
X-RateLimit-Reset: 1643720400

Idempotency

Idempotency-Key header:

@router.post("/orders")
async def create_order(
    request: CreateOrderRequest,
    idempotency_key: str = Header(...)
):
    # Check if already processed
    existing = await get_order_by_idempotency_key(idempotency_key)
    if existing:
        return existing  # Return cached result

    # Process and cache
    order = await create_order_internal(request)
    await cache_idempotency_result(idempotency_key, order, ttl=86400)
    return order

HATEOAS (Optional)

{
  "id": 123,
  "name": "John Doe",
  "links": {
    "self": "/api/v1/users/123",
    "orders": "/api/v1/users/123/orders",
    "update": "/api/v1/users/123",
    "delete": "/api/v1/users/123"
  }
}

Alternativas Consideradas

Opção 1: Header Versioning

GET /api/users
Accept: application/vnd.myapp.v1+json

Prós: - URLs mais limpos - RESTful "puro"

Contras: - Menos visível - Difícil testar (Postman, curl) - Caching mais complexo

Por que não escolhemos: URL versioning é mais explícito e fácil de usar.

Opção 2: Query Parameter Versioning

GET /api/users?version=1

Prós: - Simples - Não muda URL base

Contras: - Não semântico - Fácil esquecer - Mixing concerns

Por que não escolhemos: Menos claro que URL versioning.

Opção 3: GraphQL ao invés de REST

Prós: - Versionamento não necessário - Clientes pedem apenas o que precisam - Strongly typed

Contras: - Curva de aprendizado alta - Complexidade adicional - Caching mais difícil - Over-fetching prevention requer disciplina

Por que não escolhemos: REST é suficiente e time já conhece.

Opção 4: Deprecation 3 meses (ao invés de 6)

Prós: - Move mais rápido - Remove código legado mais cedo

Contras: - Pouco tempo para clients migrarem - Frustração de clients - Risco de churn

Por que não escolhemos: 6 meses é padrão da indústria e respeitoso com clients.

Análise de Impacto

Impacto Técnico

  • URLs versionadas: /api/v1/, /api/v2/
  • OpenAPI spec gerada automaticamente
  • Múltiplas versões coexistem
  • Deprecation warnings automáticos

Impacto em Negócio

  • ✅ APIs podem evoluir sem quebrar clients
  • ✅ Documentação sempre atualizada
  • ✅ Clients têm tempo para migrar
  • ✅ Developer experience melhor
  • ⚠️ Manutenção de múltiplas versões (overhead)

Riscos

Risco: Múltiplas versões aumentam complexidade

Mitigação: Máximo 2 versões ativas simultaneamente. Deprecation rigorosa.

Plano de Implementação

Fase 1: Standards (Semana 1)

  • Documentar URL structure standards
  • Criar templates de Pydantic models
  • Documentar error response format

Fase 2: OpenAPI (Semana 2)

  • Configurar OpenAPI export automático
  • Publicar Swagger UI
  • CI/CD valida OpenAPI spec

Fase 3: Versioning (Semana 3)

  • Implementar /api/v1/ prefix
  • Documentar deprecation policy
  • Criar migration guide template

Fase 4: Deprecation (Mês 2+)

  • Implementar deprecation warnings
  • Process para comunicar deprecations
  • Primeiro cycle de deprecation (futuro)

Métricas de Sucesso

Após 1 mês: - ✅ 100% endpoints em /api/v1/ - ✅ OpenAPI spec publicado e atualizado - ✅ Swagger UI acessível

Após 3 meses: - ✅ Zero breaking changes sem nova versão - ✅ Documentação API 100% atualizada - ✅ Feedback positivo de clients

Referências