Referência: Um Backend SaaS com FastAPI
Um layout de referência para uma API SaaS B2B multi-tenant: FastAPI 0.115+, Pydantic v2, Postgres, Redis, workers Celery e observabilidade incorporada desde o primeiro dia - implantável no Kubernetes com flags e migrações de expandir-contrair.
Receita
Cartão de receita de referência rápida - pronto para copiar e colar.
acme-api/
src/acme_api/
main.py # fábrica de app FastAPI
api/routes/ # roteadores HTTP
domain/ # lógica de negócios (sem framework)
infra/db.py # sessão SQLAlchemy
workers/tasks.py # tarefas Celery
alembic/ # migrações
tests/
Dockerfile # Python 3.14 slim + uv syncQuando usar isso:
- Backend SaaS Greenfield
- Ensinar à nova equipe o padrão da frota
- Comparar seu layout com uma linha de base
- Apêndice de RFC para ADR Greenfield
Exemplo de Trabalho
"""src/acme_api/main.py - fábrica de app SaaS mínima."""
from __future__ import annotations
from contextlib import asynccontextmanager
from fastapi import FastAPI
from pydantic import BaseModel
from acme_api.api.routes import tenants, webhooks
from acme_api.infra.db import init_db
class Health(BaseModel):
status: str
@asynccontextmanager
async def lifespan(app: FastAPI):
init_db()
yield
def create_app() -> FastAPI:
app = FastAPI(title="Acme API", version="1.0.0", lifespan=lifespan)
app.include_router(tenants.router, prefix="/v1/tenants")
app.include_router(webhooks.router, prefix="/v1/webhooks")
@app.get("/health/ready", response_model=Health)
def ready() -> Health:
return Health(status="ok")
return app
app = create_app()"""src/acme_api/domain/billing.py - lógica sem framework."""
from __future__ import annotations
from decimal import Decimal
def compute_total(subtotal: Decimal, tax_rate: Decimal) -> Decimal:
return (subtotal * (Decimal("1") + tax_rate)).quantize(Decimal("0.01"))"""src/acme_api/workers/tasks.py"""
import os
from celery import Celery
celery_app = Celery("acme", broker=os.getenv("REDIS_URL", "redis://localhost:6379/0"))
@celery_app.task(bind=True, max_retries=5)
def send_invoice_email(self, invoice_id: str) -> None:
...Diagrama de arquitetura (lógico):
Cliente → Ingress → FastAPI (workers Gunicorn/Uvicorn)
↓ SQLAlchemy
Postgres
↓ enfileirar
Redis → workers Celery → S3 / emailO que isso demonstra:
- Pacote Domain não tem importações FastAPI - núcleo testável
- Roteadores finos; modelos Pydantic nas bordas
- Celery lida com efeitos colaterais assíncronos
- Endpoint de saúde para prontidão do Kubernetes
Mergulho Profundo
Como Funciona
- Multi-tenancy -
tenant_idem linhas ou schema por tenant; middleware define variável de contexto. - Auth - JWT ou sessão via injeção de dependência; escopos por rota.
- Migrações - Alembic expandir-contrair; executado no job de release antes do tráfego.
- Observabilidade - structlog JSON + instrumentação FastAPI OpenTelemetry.
- Deploy - Imagem única para API; imagem do worker compartilha lockfile; tag
git sha.
Escolhas de Stack
| Camada | Escolha | Por quê |
|---|---|---|
| API | FastAPI 0.115+ | OpenAPI, Pydantic nativo |
| DB | Postgres 16 + SQLAlchemy 2 | Maduro, opção async mais tarde |
| Fila | Celery + Redis | Padrões de retentativas, DLQ |
| Config | pydantic-settings | Boot validado |
| Ferramentas | uv 0.6+ | CI rápido, dependências travadas |
Notas Python
FROM python:3.14.0-slim
COPY uv.lock pyproject.toml ./
RUN pip install uv && uv sync --frozen --no-dev
COPY src/ ./src/
CMD ["gunicorn", "acme_api.main:app", "-k", "uvicorn.workers.UvicornWorker"]Armadilhas
- Manipuladores de rota gordos - Monolito não testável em arquivos. Correção: serviços de domínio + roteadores finos.
- Modelos ORM compartilhados em workers sem testes de contrato - Derivação de esquema. Correção: cargas de job Pydantic versionadas.
- Guarda de tenant ausente em consultas - Vazamento de dados. Correção: camada de repositório requer tenant_id.
- DB síncrono em rotas assíncronas sem plano - Bloqueio de loop mais tarde. Correção: escolha síncrona documentada; pool de threads para CPU.
- Sem idempotência em webhooks - Efeitos colaterais duplicados. Correção: armazenamento Idempotency-Key.
Alternativas
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
| Django + DRF | CRM pesado em admin | Microserviço apenas de API |
| Dividir microserviços cedo | Escala independente comprovada | Equipe <8 |
| Serverless Lambda | Tráfego baixo e esporádico | Jobs longos estilo Celery |
| Flask | Legado mínimo | Greenfield com foco em OpenAPI |
FAQs
Quando dividir workers para um repositório separado?
Quando a cadência de release ou o domínio de falha divergem; até lá, pacotes monorepo.
SQLAlchemy Async desde o primeiro dia?
Opcional; síncrono + descarregamento de thread claro é aceitável em RPS moderado.
Como lidar com multi-região?
Réplicas de leitura, flags de residência de dados do tenant, replicar armazenamento de objetos.
Integração de faturamento?
Webhooks Stripe com tabela de idempotência; Celery para cumprimento.
Estratégia de testes?
Testes unitários pytest no domínio; integração com TestClient; testes de contrato para workers.
Flags de recursos?
Flags de ambiente pydantic-settings; migrar para fornecedor quando forem necessárias alternâncias de PM.
Versionamento de API?
Prefixo de URL /v1; mudanças que quebram obtêm /v2 com política de descontinuação.
Segredos?
SSM/External Secrets; nunca em camadas de imagem.
Limitação de taxa?
Middleware de bucket de tokens Redis por nível de tenant.
Comparação com referência Node?
Mesma disciplina de monolito modular; Python vence na adjacência de dados/ML.
Relacionado
- Referência: Um Pipeline de ML de Produção - Adjacência de ML
- Antes/Depois: Migração de Sincronização para Assíncrono - Caminho de escala
- Noções Básicas de FastAPI - Profundidade do framework
- Dockerizando Python - Construção de imagem
- Melhores Práticas de Entrega - Enviar com segurança
Versões do Stack: Esta página foi escrita para Python 3.14.0 (3.14 estável, 3.13 em manutenção), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+ e uv 0.6+.