Lição 197: Arquitetura Limpa em Swift
A Arquitetura Limpa é uma filosofia de design de software que enfatiza a separação de responsabilidades, tornando seu código mais manutenível, testável e adaptável a alterações. Nesta lição, exploraremos como implementar a Arquitetura Limpa em uma aplicação Swift.
Conceitos Chave da Arquitetura Limpa
- Entidades: Modelos de negócios centrais que encapsulam as regras de negócio.
- Casos de Uso: Regras de negócios específicas da aplicação que definem interações entre as entidades.
- Adaptadores de Interface: Código que transforma dados do formato mais conveniente para os casos de uso e entidades para o formato mais conveniente para agentes externos como a interface do usuário ou bancos de dados.
- Frameworks e Acessos: Bibliotecas externas, frameworks de interface do usuário e acesso a banco de dados que interagem com o sistema.
Estrutura do Projeto
Aqui está uma estrutura de projeto sugerida para uma aplicação Swift seguindo os princípios da Arquitetura Limpa:
MeuAppArquiteturaLimpa/
├── Entidades/
│ └── Usuario.swift
├── CasosDeUso/
│ └── ObterUsuarioCasoDeUso.swift
├── AdaptadoresDeInterface/
│ ├── UsuarioViewModel.swift
│ └── UsuarioRepositorio.swift
└── FrameworksEAcessos/
├── ViewControllers/
│ └── UsuarioViewController.swift
└── Servicos/
└── ServicoDeRede.swift
Implementando a Arquitetura Limpa em Swift
Etapa 1: Definir Entidades
Começamos com o modelo de negócios central. Para este exemplo, vamos definir uma entidade Usuario
.
// Entidades/Usuario.swift
struct Usuario {
let id: Int
let nome: String
let email: String
}
Etapa 2: Criar Casos de Uso
Em seguida, implementaremos um caso de uso que buscará um usuário. Este caso de uso interage com a entidade.
// CasosDeUso/ObterUsuarioCasoDeUso.swift
protocol UsuarioRepositorio {
func obterUsuario(by id: Int) -> Usuario?
}
class ObterUsuarioCasoDeUso {
private let usuarioRepositorio: UsuarioRepositorio
init(usuarioRepositorio: UsuarioRepositorio) {
self.usuarioRepositorio = usuarioRepositorio
}
func executar(usuarioId: Int) -> Usuario? {
return usuarioRepositorio.obterUsuario(by: usuarioId)
}
}
Etapa 3: Implementar Adaptadores de Interface
Aqui, criamos um UsuarioViewModel
para adaptar a entidade Usuario
para a interface do usuário e implementamos um repositório que simula a recuperação de dados.
// AdaptadoresDeInterface/UsuarioViewModel.swift
class UsuarioViewModel {
private let obterUsuarioCasoDeUso: ObterUsuarioCasoDeUso
var usuario: Usuario?
init(obterUsuarioCasoDeUso: ObterUsuarioCasoDeUso) {
self.obterUsuarioCasoDeUso = obterUsuarioCasoDeUso
}
func buscarUsuario(by id: Int) {
self.usuario = obterUsuarioCasoDeUso.executar(usuarioId: id)
}
}
// AdaptadoresDeInterface/UsuarioRepositorio.swift
class UsuarioRepositorioImpl: UsuarioRepositorio {
private var usuarios: [Usuario] = [
Usuario(id: 1, nome: "João Silva", email: "joao.silva@exemplo.com"),
Usuario(id: 2, nome: "Maria Silva", email: "maria.silva@exemplo.com")
]
func obterUsuario(by id: Int) -> Usuario? {
return usuarios.first { $0.id == id }
}
}
Etapa 4: Criar a Interface do Usuário
Agora vamos implementar uma interface de usuário simples para exibir os detalhes do usuário.
// FrameworksEAcessos/ViewControllers/UsuarioViewController.swift
import UIKit
class UsuarioViewController: UIViewController {
private let viewModel: UsuarioViewModel
private let usuarioId: Int
init(viewModel: UsuarioViewModel, usuarioId: Int) {
self.viewModel = viewModel
self.usuarioId = usuarioId
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) não foi implementado")
}
override func viewDidLoad() {
super.viewDidLoad()
viewModel.buscarUsuario(by: usuarioId)
exibirInformacoesDoUsuario()
}
private func exibirInformacoesDoUsuario() {
guard let usuario = viewModel.usuario else { return }
print("Informações do Usuário: \(usuario.nome) - \(usuario.email)")
}
}
Etapa 5: Configurar Injeção de Dependência
Finalmente, configuraremos a injeção de dependência para criar instâncias de nossas classes e conectar tudo.
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let usuarioRepositorio = UsuarioRepositorioImpl()
let obterUsuarioCasoDeUso = ObterUsuarioCasoDeUso(usuarioRepositorio: usuarioRepositorio)
let usuarioViewModel = UsuarioViewModel(obterUsuarioCasoDeUso: obterUsuarioCasoDeUso)
window = UIWindow(frame: UIScreen.main.bounds)
let usuarioViewController = UsuarioViewController(viewModel: usuarioViewModel, usuarioId: 1)
window?.rootViewController = usuarioViewController
window?.makeKeyAndVisible()
return true
}
}
Conclusão
Ao estruturar sua aplicação Swift de acordo com os princípios da Arquitetura Limpa, você cria uma clara separação de responsabilidades que melhora a manutenibilidade e a testabilidade. Essa estrutura facilita a adaptação a mudanças e a adição de novas funcionalidades sem afetar significativamente a base de código existente. À medida que você continua a desenvolver seu aplicativo, mantenha esses princípios em mente para um projeto mais organizado e escalável.