Monorepositório com Kotlin: organização de código e melhores práticas
Com o crescimento dos projetos e o aumento do número de serviços, os desenvolvedores se deparam com a questão: como organizar o código para que ele permaneça gerenciável, facilmente testável e rapidamente implantável? Uma das abordagens populares é o monorepositório (monorepo). Neste artigo, vamos explorar como organizar eficientemente um monorepositório em Kotlin usando Gradle, quais ferramentas e padrões aplicar e como evitar erros típicos.
O que é um monorepositório e por que ele é necessário?
Um monorepositório é uma abordagem na qual o código de vários projetos (módulos, bibliotecas, serviços) é armazenado em um único repositório. No contexto do Kotlin, isso significa que você pode ter um projeto Gradle comum, dentro do qual estão módulos para serviços backend, aplicativos Android, bibliotecas compartilhadas e até documentação.
Vantagens do monorepositório:
- Versionamento unificado — todos os módulos usam a mesma versão do Kotlin e das dependências comuns.
- Refatoração simplificada — alterações em uma biblioteca compartilhada são imediatamente visíveis para todos os consumidores.
- Configuração compartilhada de CI/CD — um único pipeline para todos os módulos.
- Colaboração aprimorada — os desenvolvedores veem todo o código do projeto.
No entanto, um monorepositório exige disciplina: sem uma organização adequada, ele pode se transformar em um "monólito" com dependências confusas. Kotlin e Gradle fornecem excelentes ferramentas para lidar com isso.
Estrutura de um monorepositório em Kotlin
Vamos considerar uma estrutura típica de projeto, onde há um serviço backend em Ktor, uma biblioteca compartilhada para modelos de dados e um aplicativo Android:
my-monorepo/├── build.gradle.kts # arquivo de build raiz├── settings.gradle.kts # inclusão dos módulos├── gradle.properties # versões comuns├── common/│ ├── build.gradle.kts│ └── src/│ └── main/kotlin/│ └── com/example/common/│ └── Models.kt├── backend/│ ├── build.gradle.kts│ └── src/│ └── main/kotlin/│ └── com/example/backend/│ └── Application.kt├── android-app/│ ├── build.gradle.kts│ └── src/│ └── main/kotlin/│ └── com/example/android/│ └── MainActivity.kt└── ci/ └── build.shO arquivo settings.gradle.kts inclui os módulos:
rootProject.name = "my-monorepo"
include(":common")include(":backend")include(":android-app")O build.gradle.kts raiz geralmente contém apenas plugins e configurações comuns:
plugins { kotlin("jvm") version "1.9.22" apply false kotlin("android") version "1.9.22" apply false id("org.jetbrains.kotlin.plugin.serialization") version "1.9.22" apply false}
allprojects { group = "com.example" version = "1.0.0"}Importante: os plugins são especificados com apply false para que não sejam aplicados ao projeto raiz, mas fiquem disponíveis para os módulos filhos.
Gerenciamento de dependências em um monorepositório
Uma das principais tarefas é evitar a duplicação de versões de dependências. Para isso, utiliza-se o Gradle Version Catalog (arquivo libs.versions.toml na pasta gradle). Exemplo:
# gradle/libs.versions.toml[versions]kotlin = "1.9.22"ktor = "2.3.7"coroutines = "1.7.3"
[libraries]ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" }ktor-server-netty = { module = "io.ktor:ktor-server-netty", version.ref = "ktor" }kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
[plugins]kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.
```