Você vê com frequência a expressão “baseado em Clean Architecture” em vagas e blogs técnicos. Mas, quando abre o código, percebe que cada equipe implementa isso de um jeito. Alguns projetos têm UseCase, outros não, e as responsabilidades de Repository também variam.
O motivo da confusão é simples: Clean Architecture é um princípio, não uma estrutura específica de pastas nem uma lista de classes.
Hoje vamos esclarecer esse princípio e o papel de UseCase e Repository quando são implementados no iOS.
O ponto central é: “as dependências apontam apenas para dentro”
Clean Architecture é um conceito organizado por Robert Martin (Uncle Bob) que enxerga o app como camadas concêntricas.
- Camada mais interna: domínio. Entidades e regras de negócio. “O que este app faz?”
- Camada intermediária: UseCase. Cenários de comportamento do app
- Camada externa: UI, bancos de dados, rede e frameworks
Existe apenas uma regra. As dependências sempre apontam de fora para dentro.
O código do domínio interno não deve conhecer UIKit, URLSession nem SwiftData, que estão nas camadas externas. Já as camadas externas podem conhecer as internas. Assim, mesmo trocando o framework de UI ou alterando a API do servidor, as regras centrais do app permanecem intactas.
Como você deve ter percebido, isso amplia o DIP (Princípio da Inversão de Dependência) para o app inteiro. É aplicar “depender de abstrações, não de implementações concretas” no nível das camadas.
Repository: a fronteira que esconde “de onde vêm os dados”
Repository é a fronteira entre o domínio e as fontes de dados. O protocolo fica no domínio, e a implementação, nas camadas externas.
// Camada de domínio — URLSessionnão SwiftDatasabe nada
protocol PostRepository {
func fetchPosts(userID: String) async throws -> [Post]
}
// Camada de dados — a implementação fica fora
final class RemotePostRepository: PostRepository {
func fetchPosts(userID: String) async throws -> [Post] {
// URLSession chamada, DTO decodificação, Postconversão
}
}
O ponto é onde o protocolo fica. Como o domínio é dono da interface, ele só sabe que “é possível buscar posts”; não sabe se eles vêm do servidor, do cache ou do banco local. Se o formato da resposta do servidor mudar, basta alterar o DTO (Data Transfer Object, objeto de transferência de dados que transporta a resposta do servidor) e a implementação. Nos testes, você pode conectar um Repository falso.
UseCase: o contêiner de uma coisa que “este app faz”
Um UseCase transforma um cenário de comportamento do app em um objeto, como “carregar o feed” ou “publicar um post”.
final class LoadFeedUseCase {
private let postRepository: PostRepository
private let blockRepository: BlockRepository
func execute(userID: String) async throws -> [Post] {
let posts = try await postRepository.fetchPosts(userID: userID)
let blocked = try await blockRepository.blockedUserIDs()
return posts
.filter { !blocked.contains($0.authorID) }
.sorted { $0.createdAt > $1.createdAt }
}
}
A regra de negócio “excluir do feed os posts de usuários bloqueados” pertence ao UseCase. O que acontece se ela ficar no ViewModel? As telas de feed, perfil e busca implementariam seus próprios filtros de bloqueio, e em algum momento uma delas ficaria diferente. Ao extraí-la para o UseCase, a regra fica em um só lugar e pode ser testada sem uma tela.
Por isso, MVVM (Model-View-ViewModel) e Clean Architecture não competem entre si: são abordagens ortogonais. MVVM organiza o lado da View, enquanto Clean Architecture organiza as camadas por trás dela. No artigo anterior, dissemos que, para evitar um Massive ViewModel, é preciso “descer a lógica”; UseCase e Repository são o lugar para onde ela deve ir.
Até onde devemos adotar isso?
A armadilha da Clean Architecture é adotá-la em excesso. Se você colocar UseCase, Repository, DTO e Mapper em um app de duas telas, passar um valor exigirá atravessar cinco arquivos. O problema de boilerplate do VIPER (View·Interactor·Presenter·Entity·Router) será reproduzido.
Um critério prático é este.
- **Repository quase sempre vale a pena.**Só separar o código de rede e DB das telas já facilita testes e mudanças.
- **Adicione UseCase quando surgirem regras de negócio compartilhadas por várias telas.**Se a maioria dos UseCase apenas repassa chamadas ao Repository, ainda é cedo.
- **Separe os modelos por camada (DTO/domínio/modelo de tela) depois que o projeto crescer.**Separar tudo desde o início só acumula código de Mapper.
Resumo
- Clean Architecture não é uma estrutura de pastas, mas o princípio de que “as dependências apontam apenas para dentro (o domínio)”. É o DIP ampliado para a escala do app.
- Repository é a fronteira que esconde a origem dos dados, e o ponto central é que o domínio é dono do protocolo.
- UseCase é o lugar das regras de negócio compartilhadas por várias telas. Ele é ortogonal ao MVVM, portanto os dois podem ser usados juntos.
- O objetivo não é ter tudo. Comece pelo Repository e adicione UseCase quando as regras se acumularem.
No próximo artigo, vamos mudar de direção e abordar TCA (The Composable Architecture), que transforma o gerenciamento de estado em uma arquitetura.

![Imagem de capa de [Arquitetura iOS #6] Fundamentos da Clean Architecture](/assets/images/posts/4a332cf7-d161-401b-ab7c-f15324e48609/1.jpg)