Quando MVVM e MVC deixam de salvar e os testes se tornam um inferno
Você já escreveu um aplicativo em Swift onde, depois de seis meses, adicionar um recurso quebrava outros três, e os testes precisavam ser reescritos por completo porque o banco de dados mudou de Core Data para Realm? Se sim, você não está sozinho. As arquiteturas clássicas em camadas (MVC, MVVM) funcionam bem no início, mas se transformam em uma "bagunça" à medida que o projeto cresce.
Vou mostrar a vocês a Arquitetura Hexagonal (também conhecida como Ports & Adapters) — uma abordagem que veio do mundo Java e DDD, mas se encaixa perfeitamente em Swift. Vamos analisá-la com exemplos reais, sem "User" e "Manager" abstratos. Você verá como separar a lógica de negócios do banco de dados, UI e rede, de modo que possam ser alterados sem tocar no núcleo do aplicativo.
Isso não é mais uma teoria — é uma prática que você pode aplicar no seu projeto ainda hoje.
O que é Arquitetura Hexagonal? Em palavras simples
Imagine um hexágono (daí o nome). No centro está sua lógica de negócios pura (domínio). Nas bordas estão as portas (interfaces) através das quais o mundo externo se comunica com o núcleo. Os adaptadores são as implementações concretas: banco de dados, API, UI, sistema de arquivos.
A regra principal: o núcleo não sabe nada sobre o mundo externo. Nem sobre UIKit, nem sobre URLSession, nem sobre Core Data. Ele conhece apenas suas próprias interfaces (portas).
Exemplo da vida real: imagine uma loja online. O núcleo sabe que existe um "Carrinho" e "Finalizar pedido". Não importa de onde vêm os dados — de um websocket, de um JSON local ou do teclado. Os adaptadores se conectam às portas e traduzem os sinais externos em comandos de domínio.
Por que vale a pena usar isso em projetos Swift?
- Testabilidade — você testa o domínio sem banco de dados real e rede. Basta substituir os adaptadores por mocks.
- Flexibilidade — quer migrar do Firebase para seu próprio servidor? Mude um adaptador, não metade do aplicativo.
- Legibilidade — a lógica de negócios não está espalhada pelos ViewControllers. Ela está em um único lugar.
- Independência de frameworks — seu núcleo não importa UIKit, Combine ou Alamofire.
Estrutura do projeto com Arquitetura Hexagonal em Swift
Vamos direto ao código. Suponha que estamos escrevendo um aplicativo para gerenciamento de tarefas (Todolist). Veja como será a estrutura de pastas:
App/├── Domain/│ ├── Entity/│ │ └── Task.swift│ ├── UseCase/│ │ └── CreateTaskUseCase.swift│ └── Port/│ ├── TaskRepository.swift│ └── TaskOutputPort.swift├── Adapter/│ ├── Persistence/│ │ └── CoreDataTaskRepository.swift│ ├── Network/│ │ └── APITaskRepository.swift│ └── Presentation/│ └── TaskListViewModel.swift└── Main/ └── AppDelegate.swiftObserve: Domain não importa nada além de Foundation. Adapter pode importar CoreData, UIKit, Alamofire — o que for necessário.
Passo 1: Entidades de domínio e portas
Vamos começar com o mais importante — o modelo de domínio. É uma struct ou enum que descreve a entidade de negócios. Nada de NSManagedObject, nada de Codable.
// Domain/Entity/Task.swiftstruct Task { let id: UUID var title: String var isCompleted: Bool let createdAt: Date}Agora definimos a porta para o repositório (porta de saída). É um protocolo que descreve como salvamos e carregamos as tarefas.
// Domain/Port/TaskRepository.swiftprotocol TaskRepository { func fetchAll() async throws -> [Task] func save(task: Task) async throws func delete(taskID: UUID) async throws}E a porta de entrada — através da qual o mundo externo (UI, API) interage com o núcleo. Geralmente é um UseCase.
// Domain/Us