DevOps Admin

Como Usar Monorepos com Git: Subtrees, Submodules e Ferramentas como Nx em Produção Já leu

Entendendo Monorepos: O Problema que Você Precisa Resolver Um monorepo é um repositório único que contém múltiplos projetos, bibliotecas ou pacotes relacionados. Diferente de um multirepo (vários repositórios), um monorepo centraliza o controle de versão, facilitando o compartilhamento de código, sincronização de dependências e coordenação entre equipes. No entanto, a complexidade surge quando você precisa decidir como organizar esses projetos dentro do repositório sem criar acoplamentos desnecessários. O desafio real não está em ter vários projetos no mesmo repositório — está em mantê-los independentes, versionados corretamente e com pipelines de CI/CD eficientes. Quando você trabalha com monorepos, precisa resolver: Como isolar mudanças? Como versionar pacotes independentemente? Como evitar que uma mudança acidental em um projeto quebre todos os outros? É aqui que Git Subtrees, Git Submodules e ferramentas como Nx entram em cena, cada uma com uma filosofia e caso de uso diferente. Git Subtrees: Simplicidade com Custo de Flexibilidade O Conceito Fundamental Git Subtrees permite que você incorpore um

Entendendo Monorepos: O Problema que Você Precisa Resolver

Um monorepo é um repositório único que contém múltiplos projetos, bibliotecas ou pacotes relacionados. Diferente de um multirepo (vários repositórios), um monorepo centraliza o controle de versão, facilitando o compartilhamento de código, sincronização de dependências e coordenação entre equipes. No entanto, a complexidade surge quando você precisa decidir como organizar esses projetos dentro do repositório sem criar acoplamentos desnecessários.

O desafio real não está em ter vários projetos no mesmo repositório — está em mantê-los independentes, versionados corretamente e com pipelines de CI/CD eficientes. Quando você trabalha com monorepos, precisa resolver: Como isolar mudanças? Como versionar pacotes independentemente? Como evitar que uma mudança acidental em um projeto quebre todos os outros? É aqui que Git Subtrees, Git Submodules e ferramentas como Nx entram em cena, cada uma com uma filosofia e caso de uso diferente.

Git Subtrees: Simplicidade com Custo de Flexibilidade

O Conceito Fundamental

Git Subtrees permite que você incorpore um repositório dentro de outro como um diretório, mantendo o histórico do repositório original. Diferente de Submodules, Subtrees fazem uma fusão (merge) real do código, não apenas uma referência. O histórico do projeto externo fica completamente integrado ao seu repositório principal, o que significa que você pode fazer checkout de qualquer versão anterior sem dependências externas.

A vantagem principal é a simplicidade: depois que o subtree está configurado, o desenvolvedor trabalha normalmente com o repositório. Não há necessidade de comandos especiais na maioria das operações. A desvantagem é que o histórico se torna mais complexo e as operações de sincronização (push/pull) requerem sintaxe específica.

Prática: Criando e Atualizando um Subtree

Vamos supor que você tem um repositório principal chamado meu-app e quer incorporar uma biblioteca chamada utils-lib. Primeiro, você adiciona o subtree:

git subtree add --prefix=libs/utils https://github.com/seu-usuario/utils-lib.git main

Esse comando:
1. Clona o repositório utils-lib em libs/utils
2. Integra todo o histórico no seu repositório
3. Cria um commit automático

Para fazer alterações localmente e sincronizar com o repositório externo, você edita os arquivos normalmente:

# Edite os arquivos em libs/utils/
echo "export function sum(a, b) { return a + b; }" > libs/utils/src/math.ts

# Commit normalmente
git add libs/utils/
git commit -m "Adiciona função sum ao utils"

# Push para o repositório externo (subtree push)
git subtree push --prefix=libs/utils https://github.com/seu-usuario/utils-lib.git main

Para atualizar o subtree quando há mudanças no repositório original:

git subtree pull --prefix=libs/utils https://github.com/seu-usuario/utils-lib.git main --squash

A flag --squash compacta todos os commits do repositório externo em um único commit, mantendo o histórico mais limpo. Sem ela, você verá todo o histórico de desenvolvimento do projeto externo.

Quando Usar Subtrees

Use Subtrees quando:
- Você precisa de um componente externo com atualizações ocasionais
- A equipe é pequena e a simplicidade é prioridade
- Você quer que o código externo seja facilmente modificável localmente
- Não há necessidade de versionar a biblioteca externa independentemente no monorepo

Evite Subtrees quando trabalhar com muitos projetos interdependentes ou quando precisar de atualizações frequentes e bidireccionais.

Git Submodules: Referências Reutilizáveis com Complexidade

Arquitetura e Funcionamento

Git Submodules é fundamentalmente diferente de Subtrees. Submodules cria uma referência para um commit específico de outro repositório, não uma incorporação. Seu repositório principal apenas "aponta" para uma versão exata de um repositório externo. Isso permite que múltiplos projetos reutilizem a mesma biblioteca, sempre com versões garantidas.

O grande benefício é que alterações em um Submodule não afetam automaticamente o repositório principal — você controla precisamente quando fazer upgrade. A desvantagem é que Submodules requerem passos adicionais: clonar o repositório, atualizar submodules, e gerenciar referências de commits.

Prática: Configurando e Atualizando Submodules

Adicionar um Submodule:

git submodule add https://github.com/seu-usuario/utils-lib.git libs/utils

Isso cria um arquivo .gitmodules que registra a configuração:

[submodule "libs/utils"]
    path = libs/utils
    url = https://github.com/seu-usuario/utils-lib.git

Quando outra pessoa clonar seu repositório, precisa inicializar os submodules:

git clone https://github.com/seu-usuario/meu-app.git
cd meu-app
git submodule init
git submodule update

Ou, em uma única linha:

git clone --recurse-submodules https://github.com/seu-usuario/meu-app.git

Para atualizar um Submodule para a versão mais recente:

cd libs/utils
git fetch origin
git checkout origin/main  # Ou a branch que você quer
cd ../..
git add libs/utils
git commit -m "Atualiza utils-lib para a versão mais recente"

Se você quer fazer mudanças dentro de um Submodule, precisa estar em uma branch real (não em detached head):

cd libs/utils
git checkout main  # Ou a branch padrão
echo "export function multiply(a, b) { return a * b; }" >> src/math.ts
git add src/math.ts
git commit -m "Adiciona função multiply"
git push origin main
cd ../..
git add libs/utils
git commit -m "Atualiza referência de utils-lib"

Quando Usar Submodules

Use Submodules quando:
- Você tem bibliotecas compartilhadas entre múltiplos projetos principais
- Cada projeto precisa de versões específicas de dependências
- Você quer manter históricos completamente separados
- As atualizações devem ser conscientes e deliberadas

Evite Submodules quando a maioria das mudanças é bidirecional ou quando sua equipe não está confortável com a complexidade.

Nx: A Solução Moderna para Monorepos em Escala

Filosofia e Arquitetura do Nx

Nx é um framework de build system e gerenciamento de monorepos que resolve o problema diferente: em vez de se preocupar com como Git organiza o código, Nx foca em como executar, testar e fazer deploy de projetos dentro do repositório. Nx não substitui Git — ele funciona com Git para entender dependências entre projetos e otimizar builds.

A ideia central é o grafo de dependências (Dependency Graph). Quando você altera um arquivo, Nx calcula quais projetos são afetados e executa apenas os testes, builds e lints necessários. Em um monorepo com 50 aplicações, isso economiza minutos em cada pipeline CI/CD. Além disso, Nx fornece templates de projeto, código compartilhado estruturado e convenções que garantem consistência.

Prática: Criando um Monorepo com Nx

Instale Nx globalmente ou use a versão que vem com create-nx-workspace:

npx create-nx-workspace@latest meu-workspace --preset=empty
cd meu-workspace

Agora crie duas aplicações: uma API e uma biblioteca compartilhada:

nx generate @nx/node:app api --directory=apps/api
nx generate @nx/react:app dashboard --directory=apps/dashboard
nx generate @nx/js:lib shared-utils --directory=libs/shared-utils

Seu workspace agora tem essa estrutura:

meu-workspace/
├── apps/
│   ├── api/
│   │   ├── src/
│   │   ├── project.json
│   │   └── ...
│   └── dashboard/
├── libs/
│   └── shared-utils/
│       ├── src/
│       │   └── lib/
│       │       └── utils.ts
│       └── project.json
├── nx.json
└── package.json

Vamos criar uma função na biblioteca compartilhada:

// libs/shared-utils/src/lib/utils.ts
export function formatCurrency(value: number): string {
  return new Intl.NumberFormat('pt-BR', {
    style: 'currency',
    currency: 'BRL'
  }).format(value);
}

export function calculateTax(amount: number, rate: number): number {
  return amount * (rate / 100);
}

Agora a API e o dashboard podem usar essas funções. No Nx, para importar de outra biblioteca, use paths configurados no tsconfig.base.json:

// apps/api/src/main.ts
import { calculateTax } from '@meu-workspace/shared-utils';

const price = 100;
const taxValue = calculateTax(price, 15);
console.log(`Imposto: R$ ${taxValue}`);
// apps/dashboard/src/App.tsx
import { formatCurrency } from '@meu-workspace/shared-utils';

export function App() {
  return <div>Preço: {formatCurrency(999.99)}</div>;
}

Quando você altera um arquivo em shared-utils e quer saber o impacto:

nx graph

Isso abre um visualizador interativo mostrando como api e dashboard dependem de shared-utils. Agora, para compilar apenas o que foi afetado:

nx affected:build

Ou execute testes apenas dos projetos afetados:

nx affected:test

Cacheing e Performance

Uma das maiores vantagens do Nx é o cache distribuído. Se você compilar a API e depois voltar para a main branch, compilar novamente é instantâneo porque Nx armazena em cache:

nx build api  # Compila normalmente
git checkout main  # Muda de branch
git checkout feature/nova-api  # Volta à branch anterior
nx build api  # Retorna do cache em milissegundos!

Você pode usar cache remoto (GitHub Actions, GitLab CI, ou Nx Cloud) para que diferentes máquinas compartilhem o cache:

nx connect-to-nx-cloud

Após isso, toda máquina na sua equipe e no CI se beneficia do cache compartilhado.

Quando Usar Nx

Use Nx quando:
- Você tem um monorepo com 5+ projetos relacionados
- CI/CD é lento por causa de builds desnecessários
- Você precisa atualizar código compartilhado frequentemente
- A equipe quer padronização e geração automática de código
- Você trabalha com diferentes tecnologias (Node, React, Angular, Vue) no mesmo repositório

Nx é overkill para repositórios com apenas 2-3 projetos simples ou para monorepos onde a maioria dos projetos é completamente independente.

Comparação Prática: Escolhendo a Estratégia Correta

Considere este cenário: você tem uma startup com três projetos: API em Node, frontend em React e uma CLI em Node. Todos compartilham funções utilitárias.

Com Subtrees: Você teria um repositório principal e adicionaria a biblioteca utils como subtree. Rápido de configurar, mas atualizações são manuais e o histórico fica complexo.

Com Submodules: Cada desenvolvedor precisaria fazer git submodule update frequentemente. A CI/CD fica mais complexa e onboarding é mais lento.

Com Nx: Você define dependências uma vez, escreve código compartilhado em libs/shared-utils, e Nx automaticamente entende que alterar uma função ali impacta API, frontend e CLI. Tests e builds são otimizados. Onboarding é simples: npm install && nx serve api.

A escolha depende da escala:
- 1-2 projetos pequenos: Submodules é suficiente
- 3-5 projetos relacionados: Comece com Subtrees para simplicidade
- 5+ projetos ou equipes múltiplas: Nx é o investimento que compensa

Conclusão

Monorepos resolvem um problema real de coordenação, mas trazem complexidade. Git Subtrees oferece simplicidade para incorporação ocasional, perfeito quando você apenas quer incluir um código externo sem gerenciamento contínuo. Git Submodules é mais robusto para referências controladas, ideal quando múltiplos projetos compartilham a mesma biblioteca em versões específicas. Nx é a resposta quando você precisa escalar: não é apenas sobre organizar código no Git, mas sobre tornar builds, testes e deploys inteligentes e rápidos.

O erro mais comum é começar com Nx quando dois Submodules resolvem o problema, ou ficar com Subtrees quando Nx economizaria horas de CI/CD. A resposta correta é sempre: qual é o tamanho do meu time, quantos projetos tenho, e com que frequência código é compartilhado?

Referências


Artigos relacionados