Introdução: por que monorepositório em projetos Java?
Com o crescimento da complexidade de aplicações Java, as equipes frequentemente enfrentam um dilema: armazenar todo o código em um único repositório (monorepositório) ou dividi-lo em vários repositórios de microsserviços (polirepositório). A prática mostra que o monorepositório é especialmente eficaz para projetos com lógica de negócios, bibliotecas e configurações compartilhadas. Em Java, ele permite gerenciar centralizadamente dependências, versões e builds, simplificando refatoração e integração.
Neste artigo, vamos analisar como organizar código Java em um monorepositório na prática: desde a estrutura de pastas até a configuração de CI/CD. Você aprenderá como evitar erros típicos e acelerar o desenvolvimento.
1. Estrutura do monorepositório para Java
O princípio chave do monorepositório é a modularidade. Em Java, isso é implementado através de projetos multimódulo (multi-module project). Cada módulo é responsável por sua área de responsabilidade: bibliotecas compartilhadas, serviços, utilitários.
Exemplo de estrutura típica:
my-java-monorepo/├── build.gradle # Arquivo de build raiz├── settings.gradle # Conecta todos os módulos├── gradle.properties # Versões compartilhadas de dependências├── common-lib/ # Biblioteca compartilhada (DTO, utilitários)│ ├── build.gradle│ └── src/├── service-auth/ # Serviço de autenticação│ ├── build.gradle│ └── src/├── service-payment/ # Serviço de pagamento│ ├── build.gradle│ └── src/└── integration-tests/ # Testes de integração ├── build.gradle └── src/Para gerenciar versões, é conveniente colocar dependências compartilhadas no arquivo raiz build.gradle ou pom.xml (para Maven). No Gradle, isso é feito através do bloco subprojects:
subprojects { apply plugin: 'java' group = 'com.pythonlib' version = '1.0.0'
repositories { mavenCentral() }
dependencies { testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0' }}Essa estrutura permite adicionar facilmente novos serviços sem copiar configurações e garante que todos os módulos usem as mesmas versões de bibliotecas.
2. Gerenciamento de dependências e versões
Um dos principais problemas do monorepositório é o conflito de versões. Em Java, para resolvê-lo, são usados BOM (Bill of Materials) ou descrição centralizada de dependências. Vamos considerar um exemplo no Gradle usando java-platform.
Criaremos um módulo platform que define todas as dependências externas:
// platform/build.gradleplugins { id 'java-platform'}
javaPlatform { allowDependencies()}
dependencies { constraints { api 'com.fasterxml.jackson.core:jackson-databind:2.15.2' api 'org.springframework.boot:spring-boot-starter-web:3.1.5' api 'org.slf4j:slf4j-api:2.0.9' }}Agora, em cada serviço, podemos simplesmente referenciar a plataforma:
// service-auth/build.gradledependencies { implementation platform(project(':platform')) implementation 'com.fasterxml.jackson.core:jackson-databind' implementation 'org.springframework.boot:spring-boot-starter-web'}Isso garante que todos os módulos usem as mesmas versões. Ao atualizar a versão em platform, as alterações são aplicadas automaticamente a todos os serviços.
Dica: para Maven, use <dependencyManagement> no pom.xml raiz. Funciona de forma semelhante.
3. CI/CD para monorepositório
A organização da integração contínua em um monorepositório requer uma abordagem especial. Não é possível compilar todos os módulos a cada commit — isso tornaria o pipeline mais lento. A solução ideal é a compilação incremental com base nas alterações.
Exemplo no GitHub Actions usando paths:
name: Java CIon: ```