Monorepositório com Swift: organização de código na prática
No desenvolvimento moderno para iOS e macOS, a escolha da estratégia de gerenciamento de código é um dos principais fatores que afetam a escalabilidade, a velocidade de entrega e a qualidade do produto. Tradicionalmente, os desenvolvedores usavam multirrepositórios (polyrepo), onde cada módulo ou serviço vive em um repositório Git separado. No entanto, com o crescimento de projetos e equipes, surge cada vez mais a questão: como organizar o código para que ele permaneça coeso, reutilizável e facilmente mantível? A resposta é — monorepositório (monorepo) com Swift. Neste artigo, vamos analisar o que é um monorepositório, por que ele é adequado para projetos Swift e como organizá-lo na prática usando o Swift Package Manager (SPM).
O que é um monorepositório e por que ele é necessário em Swift?
Monorepositório é uma abordagem onde todo o código do projeto (ou de vários projetos relacionados) é armazenado em um único repositório. Ao contrário do polyrepo, onde cada componente é isolado, o monorepo une bibliotecas, aplicativos, testes, configurações e documentação sob um único controle de versão.
Para desenvolvedores Swift, o monorepositório é especialmente relevante por várias razões:
- Versão única de dependências: Você evita a dessincronização de versões de bibliotecas entre diferentes aplicativos.
- Simplificação de refatoração: Alterações em um módulo comum são imediatamente visíveis para todos os consumidores dentro do repositório.
- Integração rápida: Novas funcionalidades podem ser testadas e lançadas em um único pull request.
- Compatibilidade com SPM: O Swift Package Manager é ideal para monorepositórios, pois suporta pacotes locais e caminhos relativos.
Estrutura de um monorepositório para um projeto Swift
Vamos considerar um exemplo prático. Suponha que temos um aplicativo para iOS (em SwiftUI) e macOS (em AppKit), que usam módulos comuns: camada de rede, lógica de autenticação e componentes de UI. Vamos organizar o monorepositório da seguinte forma:
MyMonorepo/├── Package.swift├── Sources/│ ├── AppiOS/│ │ └── main.swift│ ├── AppMacOS/│ │ └── main.swift│ ├── CoreNetworking/│ │ ├── Sources/│ │ │ └── HTTPClient.swift│ │ └── Tests/│ ├── CoreAuth/│ │ ├── Sources/│ │ │ └── AuthService.swift│ │ └── Tests/│ └── UIComponents/│ ├── Sources/│ │ └── CustomButton.swift│ └── Tests/├── Tests/│ ├── AppiOSTests/│ └── AppMacOSTests/├── .gitignore└── README.mdO arquivo chave é Package.swift. Ele descreve todos os módulos e suas dependências. Exemplo desse arquivo:
// swift-tools-version:5.9import PackageDescription
let package = Package( name: "MyMonorepo", platforms: [ .iOS(.v17), .macOS(.v14) ], products: [ .executable(name: "AppiOS", targets: ["AppiOS"]), .executable(name: "AppMacOS", targets: ["AppMacOS"]), .library(name: "CoreNetworking", targets: ["CoreNetworking"]), .library(name: "CoreAuth", targets: ["CoreAuth"]), .library(name: "UIComponents", targets: ["UIComponents"]) ], dependencies: [ // Dependências externas, se necessário // .package(url: "https://github.com/...", from: "1.0.0") ], targets: [ .executableTarget( name: "AppiOS", dependencies: ["CoreNetworking", "CoreAuth", "UIComponents"], path: "Sources/AppiOS" ), .executableTarget( name: "AppMacOS", dependencies: ["CoreNetworking", "CoreAuth", "UIComponents"], path: "Sources/AppMacOS" ), .target( name: "CoreNetworking", dependencies: [], path: "Sources/CoreNetworking/Sources" ), .target( name: "CoreAuth",
```