DevOps & CI/CD

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

15 min de leitura

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

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

<h2>Entendendo Monorepos: O Problema que Você Precisa Resolver</h2>

<p>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 <em>como</em> organizar esses projetos dentro do repositório sem criar acoplamentos desnecessários.</p>

<p>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.</p>

<h2>Git Subtrees: Simplicidade com Custo de Flexibilidade</h2>

<h3>O Conceito Fundamental</h3>

<p>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.</p>

<p>A vantagem principal é a <strong>simplicidade</strong>: 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.</p>

<h3>Prática: Criando e Atualizando um Subtree</h3>

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

<pre><code class="language-bash">git subtree add --prefix=libs/utils https://github.com/seu-usuario/utils-lib.git main</code></pre>

<p>Esse comando:</p>

<ol>

<li>Clona o repositório <code>utils-lib</code> em <code>libs/utils</code></li>

<li>Integra todo o histórico no seu repositório</li>

<li>Cria um commit automático</li>

</ol>

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

<pre><code class="language-bash"># Edite os arquivos em libs/utils/

echo &quot;export function sum(a, b) { return a + b; }&quot; &gt; libs/utils/src/math.ts

Commit normalmente

git add libs/utils/

git commit -m &quot;Adiciona função sum ao utils&quot;

Push para o repositório externo (subtree push)

git subtree push --prefix=libs/utils https://github.com/seu-usuario/utils-lib.git main</code></pre>

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

<pre><code class="language-bash">git subtree pull --prefix=libs/utils https://github.com/seu-usuario/utils-lib.git main --squash</code></pre>

<p>A flag <code>--squash</code> 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.</p>

<h3>Quando Usar Subtrees</h3>

<p>Use Subtrees quando:</p>

<ul>

<li>Você precisa de um componente externo com atualizações ocasionais</li>

<li>A equipe é pequena e a simplicidade é prioridade</li>

<li>Você quer que o código externo seja facilmente modificável localmente</li>

<li>Não há necessidade de versionar a biblioteca externa independentemente no monorepo</li>

</ul>

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

<h2>Git Submodules: Referências Reutilizáveis com Complexidade</h2>

<h3>Arquitetura e Funcionamento</h3>

<p>Git Submodules é fundamentalmente diferente de Subtrees. Submodules cria uma <strong>referência</strong> para um commit específico de outro repositório, não uma incorporação. Seu repositório principal apenas &quot;aponta&quot; 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.</p>

<p>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.</p>

<h3>Prática: Configurando e Atualizando Submodules</h3>

<p>Adicionar um Submodule:</p>

<pre><code class="language-bash">git submodule add https://github.com/seu-usuario/utils-lib.git libs/utils</code></pre>

<p>Isso cria um arquivo <code>.gitmodules</code> que registra a configuração:</p>

<pre><code class="language-ini">[submodule &quot;libs/utils&quot;]

path = libs/utils

url = https://github.com/seu-usuario/utils-lib.git</code></pre>

<p>Quando outra pessoa clonar seu repositório, precisa inicializar os submodules:</p>

<pre><code class="language-bash">git clone https://github.com/seu-usuario/meu-app.git

cd meu-app

git submodule init

git submodule update</code></pre>

<p>Ou, em uma única linha:</p>

<pre><code class="language-bash">git clone --recurse-submodules https://github.com/seu-usuario/meu-app.git</code></pre>

<p>Para atualizar um Submodule para a versão mais recente:</p>

<pre><code class="language-bash">cd libs/utils

git fetch origin

git checkout origin/main # Ou a branch que você quer

cd ../..

git add libs/utils

git commit -m &quot;Atualiza utils-lib para a versão mais recente&quot;</code></pre>

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

<pre><code class="language-bash">cd libs/utils

git checkout main # Ou a branch padrão

echo &quot;export function multiply(a, b) { return a * b; }&quot; &gt;&gt; src/math.ts

git add src/math.ts

git commit -m &quot;Adiciona função multiply&quot;

git push origin main

cd ../..

git add libs/utils

git commit -m &quot;Atualiza referência de utils-lib&quot;</code></pre>

<h3>Quando Usar Submodules</h3>

<p>Use Submodules quando:</p>

<ul>

<li>Você tem bibliotecas compartilhadas entre múltiplos projetos principais</li>

<li>Cada projeto precisa de versões específicas de dependências</li>

<li>Você quer manter históricos completamente separados</li>

<li>As atualizações devem ser conscientes e deliberadas</li>

</ul>

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

<h2>Nx: A Solução Moderna para Monorepos em Escala</h2>

<h3>Filosofia e Arquitetura do Nx</h3>

<p>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 <strong>como executar, testar e fazer deploy</strong> de projetos dentro do repositório. Nx não substitui Git — ele funciona <em>com</em> Git para entender dependências entre projetos e otimizar builds.</p>

<p>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.</p>

<h3>Prática: Criando um Monorepo com Nx</h3>

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

<pre><code class="language-bash">npx create-nx-workspace@latest meu-workspace --preset=empty

cd meu-workspace</code></pre>

<p>Agora crie duas aplicações: uma API e uma biblioteca compartilhada:</p>

<pre><code class="language-bash">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</code></pre>

<p>Seu workspace agora tem essa estrutura:</p>

<pre><code>meu-workspace/

├── apps/

│ ├── api/

│ │ ├── src/

│ │ ├── project.json

│ │ └── ...

│ └── dashboard/

├── libs/

│ └── shared-utils/

│ ├── src/

│ │ └── lib/

│ │ └── utils.ts

│ └── project.json

├── nx.json

└── package.json</code></pre>

<p>Vamos criar uma função na biblioteca compartilhada:</p>

<pre><code class="language-typescript">// libs/shared-utils/src/lib/utils.ts

export function formatCurrency(value: number): string {

return new Intl.NumberFormat(&#039;pt-BR&#039;, {

style: &#039;currency&#039;,

currency: &#039;BRL&#039;

}).format(value);

}

export function calculateTax(amount: number, rate: number): number {

return amount * (rate / 100);

}</code></pre>

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

<pre><code class="language-typescript">// apps/api/src/main.ts

import { calculateTax } from &#039;@meu-workspace/shared-utils&#039;;

const price = 100;

const taxValue = calculateTax(price, 15);

console.log(Imposto: R$ ${taxValue});</code></pre>

<pre><code class="language-typescript">// apps/dashboard/src/App.tsx

import { formatCurrency } from &#039;@meu-workspace/shared-utils&#039;;

export function App() {

return &lt;div&gt;Preço: {formatCurrency(999.99)}&lt;/div&gt;;

}</code></pre>

<p>Quando você altera um arquivo em <code>shared-utils</code> e quer saber o impacto:</p>

<pre><code class="language-bash">nx graph</code></pre>

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

<pre><code class="language-bash">nx affected:build</code></pre>

<p>Ou execute testes apenas dos projetos afetados:</p>

<pre><code class="language-bash">nx affected:test</code></pre>

<h3>Cacheing e Performance</h3>

<p>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:</p>

<pre><code class="language-bash">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!</code></pre>

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

<pre><code class="language-bash">nx connect-to-nx-cloud</code></pre>

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

<h3>Quando Usar Nx</h3>

<p>Use Nx quando:</p>

<ul>

<li>Você tem um monorepo com 5+ projetos relacionados</li>

<li>CI/CD é lento por causa de builds desnecessários</li>

<li>Você precisa atualizar código compartilhado frequentemente</li>

<li>A equipe quer padronização e geração automática de código</li>

<li>Você trabalha com diferentes tecnologias (Node, React, Angular, Vue) no mesmo repositório</li>

</ul>

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

<h2>Comparação Prática: Escolhendo a Estratégia Correta</h2>

<p>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.</p>

<p><strong>Com Subtrees</strong>: 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.</p>

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

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

<p>A escolha depende da escala:</p>

<ul>

<li><strong>1-2 projetos pequenos</strong>: Submodules é suficiente</li>

<li><strong>3-5 projetos relacionados</strong>: Comece com Subtrees para simplicidade</li>

<li><strong>5+ projetos ou equipes múltiplas</strong>: Nx é o investimento que compensa</li>

</ul>

<h2>Conclusão</h2>

<p>Monorepos resolvem um problema real de coordenação, mas trazem complexidade. <strong>Git Subtrees</strong> oferece simplicidade para incorporação ocasional, perfeito quando você apenas quer incluir um código externo sem gerenciamento contínuo. <strong>Git Submodules</strong> é mais robusto para referências controladas, ideal quando múltiplos projetos compartilham a mesma biblioteca em versões específicas. <strong>Nx</strong> é 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.</p>

<p>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?</p>

<h2>Referências</h2>

<ul>

<li><a href="https://git-scm.com/book/en/v2/Git-Tools-Subtrees" target="_blank" rel="noopener noreferrer">Git Subtree Documentation</a> — Documentação oficial do Git sobre Subtrees</li>

<li><a href="https://git-scm.com/book/en/v2/Git-Tools-Submodules" target="_blank" rel="noopener noreferrer">Git Submodules Guide</a> — Documentação oficial do Git sobre Submodules</li>

<li><a href="https://nx.dev/docs/getting-started" target="_blank" rel="noopener noreferrer">Nx Documentation - Getting Started</a> — Guia oficial de início com Nx</li>

<li><a href="https://nx.dev/docs/concepts/monorepo-structure" target="_blank" rel="noopener noreferrer">Monorepo Workspaces - Nx Best Practices</a> — Estruturas recomendadas para monorepos com Nx</li>

<li><a href="https://www.youtube.com/watch?v=pZKN2n7sJcg" target="_blank" rel="noopener noreferrer">Effective Monorepo Architecture by Dan Shappir</a> — Palestra sobre arquitetura de monorepos em larga escala</li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em DevOps & CI/CD

Como Usar Shell Scripting Avançado: Processos, Sinais, Trap e Scripts Robustos em Produção
Como Usar Shell Scripting Avançado: Processos, Sinais, Trap e Scripts Robustos em Produção

Entendendo Processos em Shell Um processo em Unix/Linux é uma instância de um...

Guia Completo de Git Avançado: Rebase, Cherry-pick, Bisect e Recuperação de Histórico
Guia Completo de Git Avançado: Rebase, Cherry-pick, Bisect e Recuperação de Histórico

Git Avançado: Rebase, Cherry-pick, Bisect e Recuperação de Histórico Introduç...

Dominando Tekton em Kubernetes: Pipelines Cloud-Native do Zero em Projetos Reais
Dominando Tekton em Kubernetes: Pipelines Cloud-Native do Zero em Projetos Reais

O que é Tekton e por que você precisa aprender Tekton é um framework open-sou...