Capítulo 08: Modelagem Rica de Domínio: Enumerações (Enum), Composição e Dataclasses (slots=True)
Especialização em Sistemas com Python & POO com Type Hints • Capítulo 08
🗺️ Mapa Conceitual do Tópico
flowchart TD
subgraph DDD_CONCEPTS["Design Orientado a Domínio (DDD Tático)"]
ENTITY["Entidades de Domínio\nIdentidade Única (UUID) · Ciclo de Vida Mutável"]
VO["Objetos de Valor (Value Objects)\nImutáveis por Definição · Igualdade Estrutural por Atributos"]
end
subgraph ENUMS["Enumerações Modernas (Python 3.11+)"]
STRENUM["StrEnum: Serialização Direta para JSON/APIs"]
AUTO["enum.auto() & Membros com Comportamento de Negócio"]
end
subgraph DATACLASSES["Dataclasses de Alta Performance (PEP 557)"]
SLOTS_DC["slots=True: Eliminação do __dict__ & Redução de 45% na RAM"]
FROZEN["frozen=True: Imutabilidade Nativa & Geração de __hash__"]
POST_INIT["__post_init__: Validação de Invariantes & Regras de Negócio"]
FIELD["field(default_factory=list, repr=False, compare=False)"]
end
DDD_CONCEPTS --> DATACLASSES
ENUMS --> ENTITY
DATACLASSES --> PROD["Microsserviços de Alta Confiabilidade & APIs Tipadas"]
style DDD_CONCEPTS fill:#ede7f6,stroke:#512da8,stroke-width:2px
style ENUMS fill:#e1f5fe,stroke:#0288d1,stroke-width:2px
style DATACLASSES fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
🏛️ 1. DDD Tático em Python: Entidades vs Objetos de Valor (Value Objects)
A modelagem de domínio corporativo moderna rejeita o anti-pattern do Modelo Anêmico (classes que funcionam apenas como sacos de dados getters/setters sem regras de negócio):
- Entidades (Entities): Objetos que possuem identidade contínua ao longo do tempo (ex.:
Cliente,Contrato,Pedido). Dois clientes com o mesmo nome e mesmo endereço são instâncias distintas se seus IDs forem diferentes. - Objetos de Valor (Value Objects): Elementos descritivos do domínio que não possuem identidade própria conceitual e são estritamente imutáveis (ex.:
Dinheiro(quantia, moeda),Endereco,Cpf,CoordenadaGPS). Dois objetos de valor com os mesmos atributos são idênticos e intercambiáveis.
from dataclasses import dataclass
from decimal import Decimal
# Value Object Canônico em Python:
@dataclass(frozen=True, slots=True)
class Dinheiro:
quantia: Decimal
moeda: str = "BRL"
def __add__(self, outro: "Dinheiro") -> "Dinheiro":
if self.moeda != outro.moeda:
raise ValueError(f"Incompatibilidade de moedas: {self.moeda} e {outro.moeda}")
return Dinheiro(quantia=self.quantia + outro.quantia, moeda=self.moeda)
🏷️ 2. Enumerações Modernas: Enum e StrEnum (Python 3.11+)
A partir do Python 3.11, a biblioteca padrão introduziu o StrEnum, no qual os membros herdam nativamente de str. Isso permite serialização direta para JSON em frameworks como FastAPI ou SQLAlchemy sem conversões manuais:
| Classe Base | Herança Primitiva | Comportamento de Comparação | Serialização JSON / DB |
|---|---|---|---|
enum.Enum |
(Nenhuma) | Comparável estritamente por identidade (is) e igualdade de enum. |
Requer .value ou .name explícito. |
enum.StrEnum |
str |
Compara diretamente com strings literais: status == "ATIVO". |
Nativa: Converte direto para string. |
enum.IntEnum |
int |
Compara com inteiros numéricos (status == 1). |
Serializa como inteiro na PVM. |
from enum import StrEnum, auto
class StatusContrato(StrEnum):
RASCUNHO = auto() # Atribui "rascunho" em lowercase automaticamente
EM_ANALISE = auto()
VIGENTE = auto()
SUSPENSO = auto()
CANCELADO = auto()
# Comparação transparente e tipada:
if status == StatusContrato.VIGENTE:
print("Contrato apto para faturamento mensal.")
⚡ 3. O Arsenal do Decorador @dataclass (PEP 557)
O decorador @dataclass gera automaticamente métodos especiais baseados nas anotações de tipo dos atributos:
| Parâmetro | Tipo | Finalidade de Engenharia |
|---|---|---|
slots=True |
bool |
Aloca slots estáticos em C; elimina __dict__ e reduz em até 45% o consumo de RAM. |
frozen=True |
bool |
Impede reatribuição de atributos após instanciação e gera o método __hash__() nativo. |
kw_only=True |
bool |
Obriga que todos os argumentos no construtor sejam passados por palavra-chave (Named Arguments). |
order=True |
bool |
Gera métodos de comparação relacional (<, <=, >, >=) baseados na ordem dos campos. |
Validações de Invariantes com __post_init__
Em dataclasses, o método __init__ gerado pelo compilador invoca __post_init__(self) ao final da inicialização. É o local canônico para validar regras de negócio antes de liberar a instância para o sistema:
from dataclasses import dataclass, field
from decimal import Decimal
import re
@dataclass(slots=True)
class ItemProposta:
descricao: str
preco_unitario: Decimal
quantidade: int = field(default=1)
subtotal: Decimal = field(init=False) # Não entra no __init__
def __post_init__(self) -> None:
if self.preco_unitario <= Decimal("0.00"):
raise ValueError(f"Preço unitário deve ser positivo: {self.preco_unitario}")
if self.quantidade <= 0:
raise ValueError(f"Quantidade deve ser maior que zero: {self.quantidade}")
# Atribuição do campo calculado
self.subtotal = self.preco_unitario * Decimal(str(self.quantidade))
🧱 4. Composição Arquitetural sobre Herança
A modelagem corporativa madura estrutura sistemas combinando componentes reutilizáveis (“Tem-Um”) em vez de criar árvores hierárquicas profundas e frágeis (“É-Um”):
classDiagram
direction LR
class Endereco {
+str logradouro
+str cep
+str cidade
}
class Fornecedor {
+UUID id
+str razao_social
+Endereco endereco_matriz
}
Fornecedor *-- Endereco : Composição Forte
A composição permite que Endereco seja compartilhado e testado isoladamente sem amarrar regras de negócio do Fornecedor.