Design de Projeto — Blueprint de Arquitetura Full-Stack do PriceTracker
Design de projeto é como a planta antes de construir um prédio—se você começar a construção sem uma planta, pode descobrir que o alicerce não é forte o suficiente ao chegar no terceiro andar, e não terá escolha a não ser demolir e recomeçar. Uma boa planta não é algo que você desenha e depois descarta; ela serve como guia durante todo o processo de construção.
1. O Que Você Vai Aprender
- Análise de Requisitos: Casos de Uso Core do SaaS de Rastreamento de Preços (Usuários, Produtos, Preços, Assinaturas, Notificações)
- Design Orientado ao Domínio: Identificando Raízes de Agregação, Objetos de Valor e Eventos de Domínio
- Decisão de Seleção de Tecnologia: Justificativa para Escolher FastAPI + PostgreSQL + Redis + Celery + WebSocket
- Estratégias de Versionamento de API: Os Trade-offs Entre Versionamento por URL e por Header
- Blueprint Arquitetural: Topologia de Implantação do Charlie—Balanceamento de Carga, Separação Leitura/Escrita do Banco de Dados, Camadas de Cache
2. A História Real da Alice
(1) Dor: Quanto mais funcionalidades são adicionadas, mais bagunçado fica
O PriceTracker evoluiu de uma API simples para um serviço com um número crescente de funcionalidades, mas carecia de um design unificado—os limites entre produtos e preços eram nebulosos, a lógica de assinatura estava espalhada por 10 endpoints, e o sistema de notificações era fortemente acoplado à lógica de negócio. Toda vez que uma nova funcionalidade era adicionada, cinco arquivos tinham que ser modificados, e bugs de regressão ocorriam frequentemente.
(2) Solução com Design Orientado ao Domínio
O Design Orientado ao Domínio (DDD) identifica domínios core e limites de uma perspectiva de negócio: Agregado Product, Agregado Price, Agregado User, e Agregado Subscription—cada agregado tem limites claros e responsabilidades, e a comunicação entre agregados ocorre via eventos, eliminando interdependências.
(3) Resultado
Para adicionar novas funcionalidades, você só precisa modificar o código dentro do agregado correspondente; as mudanças não afetam mais todo o sistema. A lógica de assinatura está centralizada no agregado Subscription, e as notificações são acionadas via evento PriceUpdated, garantindo desacoplamento completo.
3. Análise de Requisitos
(1) Casos de Uso Core
| Papel | Caso de Uso | Prioridade |
|---|---|---|
| Usuário (Alice) | Cadastro/Login | P0 |
| Usuário | Consultar Preço do Produto | P0 |
| Usuário | Assinar Notificação de Alteração de Preço | P1 |
| Administrador | Importar Preços em Lote | P0 |
| Administrador | Gerenciar CRUD de Produtos | P0 |
| Front End (Bob) | Chamar a API para obter dados | P0 |
| DevOps (Charlie) | Monitorar Saúde do Sistema | P1 |
| Sistema | Extração Automática de Preços | P2 |
(1) ▶ Exemplo: Script para Ordenação de Casos de Uso por Prioridade
# scripts/prioritize_use_cases.py
use_cases = [
{"role": "User", "action": "register_login", "priority": "P0", "effort": 2},
{"role": "User", "action": "query_price", "priority": "P0", "effort": 1},
{"role": "User", "action": "subscribe_alert", "priority": "P1", "effort": 3},
{"role": "Admin", "action": "bulk_import", "priority": "P0", "effort": 5},
]
# Ordenar por prioridade; dentro da mesma prioridade, ordenar em ordem crescente por carga de trabalho
order = {"P0": 0, "P1": 1, "P2": 2}
sorted_cases = sorted(use_cases, key=lambda x: (order[x["priority"]], x["effort"]))
for uc in sorted_cases:
print(f"[{uc['priority']}] {uc['role']}: {uc['action']} (effort={uc['effort']}d)")
Saída:
# Execução Bem-sucedida
(2) Requisitos Não Funcionais
| Requisitos | Objetivos | Restrições |
|---|---|---|
| QPS | Milhões (cluster) | Nginx LB + elasticidade K8s |
| Latência P99 | < 50 ms | Cache Redis + DB Assíncrono |
| Disponibilidade | 99,9% | Múltiplas réplicas + Recuperação Automática |
| Volume de Dados | Milhões de produtos + dezenas de milhões de preços | Particionamento PostgreSQL |
4. Design do Modelo de Domínio
(1) Diagrama ER
erDiagram
User ||--o{ Subscription : has
User ||--o{ Product : creates
User ||--o{ Alert : sets
Product ||--o{ Price : has
Product ||--o{ Alert : watched_by
Subscription ||--o{ Feature : includes
User {
int id PK
string email UK
string hashed_password
string role
datetime created_at
}
Product {
int id PK
string name
string category
float base_price
string sku UK
int user_id FK
datetime created_at
}
Price {
int id PK
int product_id FK
float price
string currency
string source
datetime recorded_at
}
Subscription {
int id PK
int user_id FK
string plan
datetime starts_at
datetime expires_at
}
Alert {
int id PK
int user_id FK
int product_id FK
float target_price
string status
}
(2) Identificação de Raízes de Agregação
| Raiz de Agregação | Entidades Contidas | Regras de Limite |
|---|---|---|
| User | User, Subscription, Alert | O usuário possui assinaturas e alertas |
| Product | Product, Price | O produto tem histórico de preços |
| PriceImport | Informações de Lote, Status de Importação | Importação em lote é uma transação separada |
(1) ▶ Exemplo: Definições de Eventos de Domínio
# domain/events.py
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
class EventType(Enum):
PRICE_CHANGED = "price_changed"
ALERT_TRIGGERED = "alert_triggered"
USER_SUBSCRIBED = "user_subscribed"
@dataclass
class DomainEvent:
event_type: EventType
aggregate_id: int
occurred_at: datetime = field(default_factory=datetime.utcnow)
payload: dict = field(default_factory=dict)
# Uso: Evento de Alteração de Preço
price_event = DomainEvent(
event_type=EventType.PRICE_CHANGED,
aggregate_id=42,
payload={"old_price": 299.9, "new_price": 249.9, "currency": "CNY"},
)
Saída:
# Execução Bem-sucedida
5. Decisões de Seleção de Tecnologia
(1) Comparação de Modelos
| Necessidade | Opção A | Opção B | Decisão | Motivo |
|---|---|---|---|---|
| Frameworks Web | FastAPI | Flask/Django | FastAPI | Assíncrono + Auto-documentação + Verificação de Tipos |
| Banco de Dados | PostgreSQL | MySQL/MongoDB | PostgreSQL | JSONB + Driver Assíncrono Maduro |
| Cache | Redis | Memcached | Redis | Estruturas de dados ricas + persistência |
| Fila de Tarefas | Celery+Redis | RQ/Dramatiq | Celery | Ecossistema maduro + monitoramento robusto |
| Comunicação em Tempo Real | WebSocket | SSE | WebSocket | Bidirecional + Baixa Latência |
| Contêiner | Docker | Bare Metal/VM | Docker | Consistência + Orquestração |
(2) Visão Geral da Arquitetura do Sistema
flowchart TD
Client[Cliente / Bob Frontend] --> Nginx[Nginx Load Balancer]
Nginx --> API1[FastAPI Pod 1]
Nginx --> API2[FastAPI Pod 2]
Nginx --> APIN[FastAPI Pod N]
API1 --> PG_Master[(PostgreSQL Master)]
API2 --> PG_Master
APIN --> PG_Master
PG_Master --> PG_Replica[(PostgreSQL Replica)]
API1 --> Redis[(Redis Cluster)]
API2 --> Redis
APIN --> Redis
Redis --> CW1[Celery Worker 1]
Redis --> CW2[Celery Worker 2]
CW1 --> PG_Master
CW2 --> PG_Master
API1 --> WS[WebSocket Hub]
API2 --> WS
Prometheus[Prometheus] --> API1
Grafana[Grafana] --> Prometheus
6. Estratégia de Versionamento de API
(1) Versão por URL vs. Versão por Header
| Dimensão | Versão por URL /api/v1/ |
Versão por Header Accept: v=1 |
|---|---|---|
| Visibilidade | Alta (URL é explícita) | Baixa (oculta no header) |
| Roteamento | Simples (baseado em prefixo) | Complexo (requer parsing de middleware) |
| Cache | Cache de URL independente | Requer Header Vary |
| Swagger | Agrupamento automático | Requer configuração manual |
| Recomendado | ✅ Como Usar o PriceTracker | Adequado para APIs Internas |
(1) ▶ Exemplo: Estrutura de Roteamento com Versionamento por URL
from fastapi import APIRouter
# Rotas V1
v1_router = APIRouter(prefix="/api/v1", tags=["v1"])
v1_products = APIRouter(prefix="/products", tags=["products"])
v1_prices = APIRouter(prefix="/prices", tags=["prices"])
v1_auth = APIRouter(prefix="/auth", tags=["auth"])
# Rotas V2 (futuro)
v2_router = APIRouter(prefix="/api/v2", tags=["v2"])
# Montar roteadores
app.include_router(v1_auth)
app.include_router(v1_products, dependencies=[Depends(get_current_user)])
app.include_router(v1_prices, dependencies=[Depends(get_current_user)])
app.include_router(v1_router)
Saída:
# Execução Bem-sucedida
❓ Perguntas Frequentes
Product é a raiz de agregação, Price só pode ser modificado através de Product e não pode ser adicionado ou excluído independentemente, garantindo assim a consistência dos dados.publish_price_updated_event.delay(product_id, new_price)), enquanto uma abordagem mais complexa usa filas de mensagens (RabbitMQ/Kafka).📖 Resumo
- A análise de requisitos distingue entre requisitos funcionais e não funcionais e clarifica prioridades e restrições
- DDD: Identificar raízes de agregação (User, Product, PriceImport), e definir limites de transação e regras de consistência
- Seleção de tecnologia baseada em caso de uso: FastAPI (assíncrono) + PostgreSQL (JSONB) + Redis (estruturas de dados) + Celery (filas de tarefas)
- Versionamento de API usa o prefixo URL
/api/v1/, que é simples, intuitivo e amigável ao cache - Topologia de implantação: Nginx Load Balancer → FastAPI Pods → PostgreSQL Master-Slave → Redis Cluster → Celery Workers
📝 Exercícios
- Questão Básica (Dificuldade: ⭐): Desenhe um diagrama de arquitetura de sistema para o PriceTracker (incluindo FastAPI, PostgreSQL, Redis, Celery e Nginx), e rotule as responsabilidades de cada componente e o fluxo de dados. Dica: flowchart Mermaid
- Problema Avançado (Dificuldade ⭐⭐): Projete um diagrama ER para o modelo de domínio do PriceTracker, identifique três raízes de agregação (User, Product, PriceImport), e defina as entidades incluídas em cada agregado e suas regras de limite. Dica: Mermaid erDiagram + tabela de raízes de agregação
- Desafio (Dificuldade: ⭐⭐⭐): Complete o blueprint arquitetural completo do PriceTracker—incluindo o documento de requisitos (casos de uso core + requisitos não funcionais), uma tabela de comparação de tecnologias, código para a estrutura de roteamento de versionamento de API, e um diagrama de topologia de implantação (incluindo bancos de dados master-slave, camadas de cache e cluster Celery). Dica: Incorpore todo o conteúdo abordado nesta lição.
---|



