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:
- Request chega em um framework web (ex: Flask, Spring).
- O framework chama um Controller (Interface Adapter).
- O Controller converte a requisição em um DTO e chama o Use Case.
- O Use Case opera sobre Entities e chama interfaces de Gateway.
- O Gateway (implementado no círculo externo) persiste os dados.
- O Use Case retorna um resultado ao Controller.
- O Controller usa um Presenter para formatar a resposta.
- 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
- Clean Architecture: A Craftsman's Guide by Robert C. Martin — Livro fundamental que define os conceitos de Entities, Use Cases, Interface Adapters e Frameworks.
- The Clean Architecture (original blog post) — Artigo seminal de Robert C. Martin que introduziu o modelo de camadas concêntricas.
- Hexagonal Architecture (Ports and Adapters) by Alistair Cockburn — Descrição original da arquitetura hexagonal, base conceitual para a Clean Architecture.
- Clean Architecture .NET Examples by Jason Taylor — Repositório prático com implementação completa de Clean Architecture em .NET, incluindo Use Cases e Repositories.
- Martin Fowler: AnemicDomainModel — Artigo que descreve a armadilha de modelos de domínio sem comportamento, essencial para entender o papel correto das Entities.
- Refactoring Guru: Dependency Inversion Principle — Explicação clara do DIP, princípio que sustenta toda a Clean Architecture.
- Microsoft Docs: Clean Architecture for ASP.NET Core — Guia oficial da Microsoft sobre aplicação da Clean Architecture em aplicações web modernas.