Monorepositorio con Swift: organización del código en la práctica
En el desarrollo moderno de iOS y macOS, la elección de una estrategia de gestión de código es uno de los factores clave que afectan la escalabilidad, la velocidad de entrega y la calidad del producto. Tradicionalmente, los desarrolladores usaban multirepositorios (polyrepo), donde cada módulo o servicio vive en un repositorio Git separado. Sin embargo, con el crecimiento de proyectos y equipos, surge cada vez más la pregunta: ¿cómo organizar el código para que se mantenga íntegro, reutilizable y fácil de mantener? La respuesta es — monorepositorio (monorepo) con Swift. En este artículo, analizaremos qué es un monorepositorio, por qué es adecuado para proyectos Swift y cómo organizarlo en la práctica con Swift Package Manager (SPM).
¿Qué es un monorepositorio y por qué es necesario en Swift?
Un monorepositorio es un enfoque en el que todo el código del proyecto (o de varios proyectos relacionados) se almacena en un solo repositorio. A diferencia de polyrepo, donde cada componente está aislado, monorepo unifica bibliotecas, aplicaciones, pruebas, configuraciones y documentación bajo un único control de versiones.
Para los desarrolladores de Swift, el monorepositorio es especialmente relevante por varias razones:
- Versión única de dependencias: Evitas la desincronización de versiones de bibliotecas entre diferentes aplicaciones.
- Simplificación de la refactorización: Los cambios en un módulo común son visibles de inmediato para todos los consumidores dentro del repositorio.
- Integración rápida: Las nuevas funcionalidades se pueden probar y lanzar en una sola pull request.
- Compatibilidad con SPM: Swift Package Manager es ideal para monorepositorios, ya que admite paquetes locales y rutas relativas.
Estructura de un monorepositorio para un proyecto Swift
Consideremos un ejemplo práctico. Supongamos que tenemos una aplicación para iOS (en SwiftUI) y macOS (en AppKit), que utilizan módulos comunes: capa de red, lógica de autenticación y componentes de UI. Organicemos el monorepositorio de la siguiente manera:
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.mdEl archivo clave es Package.swift. Describe todos los módulos y sus dependencias. Un ejemplo de dicho archivo:
// 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: [ // Dependencias externas, si son necesarias // .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",