API Design
Padrões e convenções para design de APIs REST e comunicação assíncrona.
REST APIs
Princípios
- RESTful design
- JSON para requests e responses
- HTTP methods semânticos (GET, POST, PUT, PATCH, DELETE)
- Status codes adequados
- Versionamento quando necessário
Estrutura de URLs
# Coleções (plurais)
GET /api/v1/users # Listar usuários
POST /api/v1/users # Criar usuário
GET /api/v1/users/{id} # Obter usuário específico
PUT /api/v1/users/{id} # Atualizar usuário completo
PATCH /api/v1/users/{id} # Atualizar parcialmente
DELETE /api/v1/users/{id} # Deletar usuário
# Sub-recursos
GET /api/v1/users/{id}/orders # Pedidos do usuário
POST /api/v1/users/{id}/orders # Criar pedido
# Ações (quando REST não se aplica bem)
POST /api/v1/users/{id}/reset-password
POST /api/v1/orders/{id}/cancel
HTTP Status Codes
2xx Success:
- 200 OK - Request bem-sucedido (GET, PUT, PATCH)
- 201 Created - Recurso criado (POST)
- 204 No Content - Sucesso sem body (DELETE)
4xx Client Errors:
- 400 Bad Request - Validação falhou
- 401 Unauthorized - Não autenticado
- 403 Forbidden - Autenticado mas sem permissão
- 404 Not Found - Recurso não existe
- 409 Conflict - Conflito (ex: email já existe)
- 422 Unprocessable Entity - Validação semântica falhou
- 429 Too Many Requests - Rate limit exceeded
5xx Server Errors:
- 500 Internal Server Error - Erro não tratado
- 502 Bad Gateway - Serviço downstream falhou
- 503 Service Unavailable - Serviço temporariamente indisponível
Request/Response Format
Request (POST/PUT):
Response Success:
{
"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:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input data",
"details": [
{
"field": "email",
"message": "Invalid email format"
},
{
"field": "age",
"message": "Must be between 0 and 150"
}
]
}
}
Pagination
Response:
{
"data": [...],
"pagination": {
"page": 2,
"page_size": 20,
"total_items": 150,
"total_pages": 8,
"has_next": true,
"has_prev": true
}
}
Filtering
Autenticação
JWT Bearer Token:
FastAPI Implementation:
from fastapi import Depends, HTTPException
from fastapi.security import HTTPBearer
security = HTTPBearer()
async def get_current_user(token: str = Depends(security)) -> User:
try:
payload = jwt.decode(token.credentials, SECRET_KEY)
user_id = payload.get("sub")
user = await get_user(user_id)
if not user:
raise HTTPException(status_code=401)
return user
except JWTError:
raise HTTPException(status_code=401)
@router.get("/me")
async def get_current_user_endpoint(
current_user: User = Depends(get_current_user)
):
return current_user
Event-Driven Architecture
SQS Pattern
Producer (Lambda):
import boto3
import json
sqs = boto3.client('sqs')
def publish_user_created_event(user: User):
sqs.send_message(
QueueUrl=USER_EVENTS_QUEUE_URL,
MessageBody=json.dumps({
"event_type": "user_created",
"event_id": str(uuid.uuid4()),
"timestamp": datetime.utcnow().isoformat(),
"data": {
"user_id": user.id,
"email": user.email
}
}),
MessageAttributes={
'event_type': {
'StringValue': 'user_created',
'DataType': 'String'
}
}
)
Consumer (Lambda):
def lambda_handler(event, context):
for record in event['Records']:
message = json.loads(record['body'])
event_type = message['event_type']
if event_type == 'user_created':
handle_user_created(message['data'])
elif event_type == 'user_updated':
handle_user_updated(message['data'])
else:
logger.warning(f"Unknown event type: {event_type}")
SNS Pattern (Fan-out)
Publisher:
sns = boto3.client('sns')
def publish_product_update(product: Product):
sns.publish(
TopicArn=PRODUCT_UPDATES_TOPIC_ARN,
Message=json.dumps({
"product_id": product.id,
"action": "price_updated",
"old_price": product.old_price,
"new_price": product.price
}),
Subject="Product Price Updated"
)
Subscribers (múltiplos Lambdas):
# Lambda 1: Atualizar cache
def update_cache_handler(event, context):
message = json.loads(event['Records'][0]['Sns']['Message'])
cache.invalidate(f"product:{message['product_id']}")
# Lambda 2: Notificar usuários
def notify_users_handler(event, context):
message = json.loads(event['Records'][0]['Sns']['Message'])
users = get_interested_users(message['product_id'])
send_notifications(users, message)
# Lambda 3: Atualizar analytics
def analytics_handler(event, context):
message = json.loads(event['Records'][0]['Sns']['Message'])
track_price_change(message)
Idempotência
APIs devem ser idempotentes quando possível:
@router.post("/orders", status_code=201)
async def create_order(
request: CreateOrderRequest,
idempotency_key: str = Header(...)
):
# Verificar se já processamos este idempotency_key
existing = await get_order_by_idempotency_key(idempotency_key)
if existing:
return existing # Retornar ordem existente (idempotente)
# Criar nova ordem
order = await service.create_order(request, idempotency_key)
return order
Rate Limiting
from fastapi_limiter.depends import RateLimiter
@router.post("/login", dependencies=[Depends(RateLimiter(times=5, seconds=60))])
async def login(credentials: LoginRequest):
# Máximo 5 tentativas por minuto
...
Versionamento
Quando criar nova versão: - Breaking changes na API - Mudanças significativas no formato de resposta - Remoção de campos
Deprecação:
- Avisar clientes com 3 meses de antecedência
- Header X-API-Deprecation: version=v1, sunset=2026-06-01
- Manter v1 rodando até data de sunset