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):

  1. 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.
  2. 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.