Entities, Use Cases, Interfaces e Frameworks na Clean Architecture

1. Fundamentos da Clean Architecture e a Regra de Dependência

A Clean Architecture, proposta por Robert C. Martin em 2012, estabelece um conjunto de princípios para projetar sistemas de software que sejam testáveis, independentes de frameworks e adaptáveis a mudanças tecnológicas. O alicerce dessa arquitetura é o Princípio da Inversão de Dependência (DIP) : módulos de alto nível não devem depender de módulos de baixo nível; ambos devem depender de abstrações.

O modelo organiza o código em quatro camadas concêntricas, onde a seta de dependência sempre aponta para dentro — do círculo mais externo para o mais interno. Nenhuma camada interna conhece detalhes das camadas externas. O círculo mais interno contém as regras de negócio puras, completamente isoladas de bancos de dados, interfaces web ou bibliotecas externas.

[ Frameworks ] → [ Interface Adapters ] → [ Use Cases ] → [ Entities ]

2. Entities: As Regras de Negócio da Empresa

Entities são objetos que encapsulam as regras de negócio mais críticas e universais da empresa. Elas representam conceitos fundamentais do domínio — como Order, Customer ou Invoice — e contêm validações e comportamentos que permanecem verdadeiros independentemente do contexto da aplicação.

Uma Entity não pode conter anotações de frameworks, heranças de classes de infraestrutura ou referências a bibliotecas externas. Ela deve ser puramente implementada na linguagem de programação, sem dependências de banco de dados, serialização ou UI.

Exemplo prático: Order

class Order:
    def __init__(self, id: str, items: list, customer_id: str):
        self.id = id
        self.items = items
        self.customer_id = customer_id
        self.status = "pending"
        self._validate()

    def _validate(self):
        if not self.items:
            raise ValueError("Order must have at least one item")
        if not self.customer_id:
            raise ValueError("Customer ID is required")

    def calculate_total(self) -> float:
        return sum(item.price * item.quantity for item in self.items)

    def mark_as_shipped(self):
        if self.status != "pending":
            raise ValueError("Only pending orders can be shipped")
        self.status = "shipped"

Observe que Order não herda de BaseModel de um ORM, não possui anotações @Entity de JPA e não referencia nenhum framework. Ela é testável com um simples teste unitário.

3. Use Cases: Orquestrando o Fluxo da Aplicação

Use Cases representam as regras de negócio específicas da aplicação. Enquanto as Entities definem o que é um pedido, um Use Case define como criar um pedido, processar um pagamento ou cancelar uma compra. Eles orquestram o fluxo: recebem dados de entrada, interagem com Entities, invocam portas de saída e retornam resultados.

A dependência dos Use Cases é apenas para dentro: eles conhecem as Entities, mas não conhecem Controllers, Repositories ou Frameworks. Qualquer comunicação com o mundo externo ocorre através de interfaces definidas na própria camada de Use Cases.

Exemplo: CreateOrderUseCase

class CreateOrderUseCase:
    def __init__(self, order_repository):
        self.order_repository = order_repository  # Interface

    def execute(self, input_data: dict) -> dict:
        # Cria a Entity a partir dos dados de entrada
        order = Order(
            id=input_data["id"],
            items=[Item(**item) for item in input_data["items"]],
            customer_id=input_data["customer_id"]
        )

        # Regra de negócio específica: limite de valor
        if order.calculate_total() > 10000.0:
            raise ValueError("Order value exceeds limit")

        # Persiste através da interface (sem saber o banco)
        self.order_repository.save(order)

        # Retorna DTO de saída
        return {"order_id": order.id, "status": order.status, "total": order.calculate_total()}

4. Interface Adapters: A Ponte Entre o Mundo Interno e Externo

A camada de Interface Adapters é responsável por converter dados entre o formato mais conveniente para os Use Cases e o formato exigido por agentes externos (web, banco de dados, APIs). Ela contém Controllers, Presenters e Gateways (ou Repositories).

  • Controllers: recebem requisições HTTP (ou de qualquer outro canal), extraem os parâmetros e chamam o Use Case apropriado.
  • Presenters: convertem a saída do Use Case para o formato de resposta (JSON, XML, view model).
  • Gateways/Repositories: definem contratos (interfaces) que serão implementados na camada externa. Essa é a essência da inversão de dependência: o contrato está aqui, a implementação concreta está no círculo mais externo.

Exemplo de Controller (em Python, sem framework específico)

class OrderController:
    def __init__(self, create_order_use_case):
        self.create_order_use_case = create_order_use_case

    def handle_create(self, http_request: dict) -> dict:
        try:
            result = self.create_order_use_case.execute(http_request["body"])
            return {"status": 201, "body": result}
        except ValueError as e:
            return {"status": 400, "body": {"error": str(e)}}

Exemplo de interface de Repository

class OrderRepositoryInterface:
    def save(self, order: Order) -> None:
        raise NotImplementedError

    def find_by_id(self, order_id: str) -> Order:
        raise NotImplementedError

5. Frameworks e Drivers: O Círculo Externo

A camada mais externa contém Frameworks e Drivers — bancos de dados, bibliotecas web, sistemas de mensageria, etc. Aqui residem as implementações concretas dos contratos definidos nas camadas internas. Um framework como Spring, Django ou Express é um detalhe substituível; se amanhã você trocar de banco de dados ou de framework web, apenas essa camada é alterada.

Exemplo: Implementação concreta do Repository com SQLite

class SQLiteOrderRepository(OrderRepositoryInterface):
    def __init__(self, connection):
        self.connection = connection

    def save(self, order: Order) -> None:
        cursor = self.connection.cursor()
        cursor.execute(
            "INSERT INTO orders (id, customer_id, status, total) VALUES (?, ?, ?, ?)",
            (order.id, order.customer_id, order.status, order.calculate_total())
        )
        self.connection.commit()

    def find_by_id(self, order_id: str) -> Order:
        cursor = self.connection.cursor()
        row = cursor.execute("SELECT * FROM orders WHERE id = ?", (order_id,)).fetchone()
        if not row:
            raise ValueError("Order not found")
        return Order(id=row[0], items=[], customer_id=row[2])  # Simplificado

6. Fluxo de Dados Através das Camadas

O fluxo típico de uma requisição segue este caminho:

  1. Request chega em um framework web (ex: Flask, Spring).
  2. O framework chama um Controller (Interface Adapter).
  3. O Controller converte a requisição em um DTO e chama o Use Case.
  4. O Use Case opera sobre Entities e chama interfaces de Gateway.
  5. O Gateway (implementado no círculo externo) persiste os dados.
  6. O Use Case retorna um resultado ao Controller.
  7. O Controller usa um Presenter para formatar a resposta.
  8. A resposta é enviada de volta pelo framework.

DTOs (Data Transfer Objects) são objetos simples que transportam dados entre camadas, sem lógica de negócio. Eles evitam que entidades internas vazem para o mundo externo.

Gerenciamento de erros: exceções de negócio (como ValueError) devem ser capturadas nos adapters e convertidas em respostas HTTP apropriadas. Exceções de infraestrutura (como falha de banco) nunca devem chegar aos Use Cases.

7. Comparação com Arquiteturas Vizinhas e Melhores Práticas

Clean Architecture vs. Hexagonal Architecture: ambas compartilham o mesmo princípio de inversão de dependência, mas a Hexagonal (Ports & Adapters) organiza-se em torno de portas (interfaces) e adaptadores (implementações), sem a rigidez das quatro camadas. Na prática, são complementares: a Clean Architecture oferece uma estrutura de camadas mais explícita.

Vertical Slice Architecture: em vez de separar por camadas técnicas (entities, use cases, controllers), separa por funcionalidades (ex: "criar pedido" contém tudo que precisa — desde o controller até o acesso a dados). É uma alternativa viável para sistemas com muitos contextos delimitados.

Armadilhas comuns:
- Dependências circulares: ocorrem quando uma camada interna importa algo da externa. Solução: sempre apontar as setas para dentro.
- Anemic Domain Models: Entities sem comportamento, apenas com getters/setters. Isso transforma a lógica de negócio em scripts espalhados pelos Use Cases.
- Vazamento de frameworks: colocar anotações JPA, decoradores de injeção de dependência ou herança de classes de framework dentro das Entities.

A Clean Architecture não é uma bala de prata, mas quando aplicada com disciplina, produz sistemas que resistem à obsolescência tecnológica e são compreensíveis mesmo anos depois.

Referências