Arquitetura de Software — Python SWE Agent
Modelo de Camadas (Clean Architecture adaptada para Python)
src/
├── domain/ # Núcleo — zero dependências externas
│ ├── entities/ # Entidades com identidade
│ ├── value_objects/ # Imutáveis, comparados por valor
│ ├── aggregates/ # Raiz de consistência transacional
│ ├── repositories/ # Interfaces (ABC) — não implementações
│ ├── services/ # Domain services (lógica que não cabe em entidade)
│ └── exceptions.py # Exceções de domínio tipadas
│
├── application/ # Casos de uso — orquestra domínio
│ ├── use_cases/ # Um arquivo por caso de uso
│ ├── dtos/ # Data Transfer Objects (entrada/saída)
│ └── ports/ # Interfaces para infra (email, cache, etc.)
│
├── infrastructure/ # Implementações concretas
│ ├── persistence/ # ORM models, repositories concretos
│ ├── http/ # Clients HTTP externos
│ ├── cache/ # Redis, Memcached
│ ├── messaging/ # Kafka, SQS, Celery
│ └── config/ # Settings, env vars
│
└── interfaces/ # Adaptadores de entrada
├── api/ # FastAPI/Django routers, serializers
├── cli/ # Click commands
└── workers/ # Background jobs
SOLID em Python — Aplicação Prática
S — Single Responsibility
# ❌ RUIM: classe faz tudo
class UserService:
def create_user(self, data): ...
def send_welcome_email(self, user): ... # não é responsabilidade dela
def hash_password(self, password): ... # não é responsabilidade dela
# ✅ BOM: responsabilidades separadas
class UserService:
def __init__(self, repo: UserRepository, hasher: PasswordHasher):
self._repo = repo
self._hasher = hasher
def create_user(self, data: CreateUserDTO) -> User:
hashed = self._hasher.hash(data.password)
user = User(email=data.email, password_hash=hashed)
return self._repo.save(user)
class WelcomeEmailService:
def send(self, user: User) -> None: ...
O — Open/Closed
# Use Protocol ou ABC para extensão sem modificação
from abc import ABC, abstractmethod
class NotificationChannel(ABC):
@abstractmethod
def send(self, recipient: str, message: str) -> None: ...
class EmailChannel(NotificationChannel):
def send(self, recipient: str, message: str) -> None: ...
class SlackChannel(NotificationChannel):
def send(self, recipient: str, message: str) -> None: ...
# Novo canal = nova classe, sem tocar no existente
D — Dependency Inversion
# ❌ RUIM: depende de concreção
class OrderService:
def __init__(self):
self.db = PostgreSQLRepository() # acopla a infra
# ✅ BOM: depende de abstração
class OrderService:
def __init__(self, repo: OrderRepository): # injeta interface
self._repo = repo
Domain-Driven Design — Padrões Essenciais
Entidade vs. Value Object
from dataclasses import dataclass
from uuid import UUID, uuid4
# ENTIDADE — identidade importa, estado muda
class Order:
def __init__(self, id: UUID, customer_id: UUID):
self.id = id
self.customer_id = customer_id
self._items: list[OrderItem] = []
self._status = OrderStatus.PENDING
def add_item(self, item: OrderItem) -> None:
if self._status != OrderStatus.PENDING:
raise OrderNotEditableError(self.id)
self._items.append(item)
# VALUE OBJECT — imutável, sem identidade, comparado por valor
@dataclass(frozen=True)
class Money:
amount: Decimal
currency: str
def __post_init__(self):
if self.amount < 0:
raise ValueError("Amount cannot be negative")
def add(self, other: "Money") -> "Money":
if self.currency != other.currency:
raise CurrencyMismatchError()
return Money(self.amount + other.amount, self.currency)
Exceções de Domínio Tipadas
# domain/exceptions.py
class DomainError(Exception):
"""Base para todos os erros de domínio."""
class OrderNotEditableError(DomainError):
def __init__(self, order_id: UUID):
super().__init__(f"Order {order_id} cannot be edited in current status")
self.order_id = order_id
class InsufficientStockError(DomainError):
def __init__(self, product_id: UUID, requested: int, available: int):
super().__init__(
f"Insufficient stock for product {product_id}: "
f"requested {requested}, available {available}"
)
Repository Pattern
# domain/repositories.py — INTERFACE (sem import de ORM)
from abc import ABC, abstractmethod
class OrderRepository(ABC):
@abstractmethod
def find_by_id(self, order_id: UUID) -> Order | None: ...
@abstractmethod
def save(self, order: Order) -> Order: ...
@abstractmethod
def find_pending_by_customer(self, customer_id: UUID) -> list[Order]: ...
# infrastructure/persistence/order_repository.py — IMPLEMENTAÇÃO
class DjangoOrderRepository(OrderRepository):
def find_by_id(self, order_id: UUID) -> Order | None:
try:
model = OrderModel.objects.get(id=order_id)
return self._to_domain(model)
except OrderModel.DoesNotExist:
return None
Estrutura FastAPI — Padrão Recomendado
# interfaces/api/orders/router.py
from fastapi import APIRouter, Depends, HTTPException, status
from application.use_cases.create_order import CreateOrderUseCase
from application.dtos.order import CreateOrderRequest, OrderResponse
from interfaces.api.dependencies import get_create_order_use_case
router = APIRouter(prefix="/orders", tags=["orders"])
@router.post("/", response_model=OrderResponse, status_code=status.HTTP_201_CREATED)
async def create_order(
request: CreateOrderRequest,
use_case: CreateOrderUseCase = Depends(get_create_order_use_case),
) -> OrderResponse:
try:
order = await use_case.execute(request)
return OrderResponse.from_domain(order)
except InsufficientStockError as e:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(e))
except DomainError as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
Anti-Patterns — Sempre Evitar
| Anti-Pattern |
Problema |
Solução |
| Fat Model (Django) |
Domínio vaza para ORM |
Separar domain entity do ORM model |
| Service como namespace |
Funções estáticas sem coesão |
Use cases com dependências explícitas |
| Import circular |
Arquitetura quebrada |
Invertir dependências com ABC |
| God object |
Viola SRP, dificulta teste |
Decomposição por responsabilidade |
| Anemic Domain Model |
Lógica de negócio fora do domínio |
Encapsular comportamento nas entidades |
Para Referências Detalhadas
~/.cursor/skills/references/solid-python.md — exemplos completos de cada princípio
~/.cursor/skills/references/ddd-patterns.md — Aggregates, Domain Events, Specifications
~/.cursor/skills/references/clean-arch-django.md — adaptação para projetos Django existentes
~/.cursor/skills/references/fastapi-structure.md — estrutura completa de projeto FastAPI