Monorepo vs Multi-repo
Decisão sobre onde o código vive: num único repositório Git que abriga múltiplos projetos e pacotes (monorepo), ou em repositórios separados, cada um com seu ciclo de vida independente (multi-repo). São estratégias ortogonais à arquitetura do software — e confundi-las com monolito é a armadilha mais comum.
Intenção
Definir a estratégia de organização de repositórios Git para múltiplos projetos, pacotes ou serviços de uma organização, equilibrando visibilidade e compartilhamento de código (monorepo) com isolamento e autonomia de ciclo de vida (multi-repo).
A escolha não é sobre arquitetura do sistema — é sobre onde o código fica versionado. Empresas como Google, Meta e Microsoft mantêm virtualmente todo o seu código num único repositório gigante. Outras preferem um repositório por serviço ou produto. Ambas as abordagens funcionam em escala; os trade-offs são diferentes.
Problema
À medida que um time cresce e o número de projetos ou pacotes aumenta, surgem tensões que qualquer estratégia de repositório precisa resolver:
- Compartilhamento de código: como reutilizar um utilitário ou uma biblioteca interna sem duplicar ou publicar um pacote externo a cada mudança?
- Coordenação de mudanças: quando uma alteração numa dependência compartilhada precisa refletir em múltiplos projetos ao mesmo tempo, como garantir consistência?
- Autonomia de equipes: como permitir que times diferentes evoluam seus projetos em ritmos distintos sem travar uns aos outros?
- Visibilidade: como descobrir o que já existe para não reinventar a roda?
Monorepo e multi-repo são respostas diferentes a essas tensões, e cada um as resolve melhor em contextos distintos.
Estrutura
Multi-repo
Cada projeto, serviço ou biblioteca vive num repositório Git separado. É o modelo padrão do GitHub open-source: cada projeto tem seu próprio ciclo de releases, CI/CD, permissões e histórico.
GitHub / GitLab / Bitbucket
│
├── org/api-gateway ← repositório independente
│ ├── src/
│ ├── package.json (version: "2.1.0")
│ └── .github/workflows/
│
├── org/user-service ← repositório independente
│ ├── src/
│ ├── package.json (version: "1.5.3")
│ └── .github/workflows/
│
├── org/shared-utils ← biblioteca publicada no npm/registry
│ ├── src/
│ └── package.json (version: "0.9.0", name: "@org/shared-utils")
│
└── org/frontend-app ← repositório independente
├── src/
│ └── package.json (depends on "@org/shared-utils": "^0.9.0")
└── .github/workflows/
Monorepo
Todos os projetos e pacotes convivem num único repositório Git. O histórico é compartilhado; mudanças que afetam múltiplos pacotes aparecem num único commit ou PR.
org/monorepo ← único repositório Git
│
├── apps/
│ ├── api-gateway/ ← projeto: app Node/Express
│ │ └── src/
│ └── frontend/ ← projeto: app React/Vue
│ └── src/
│
├── packages/
│ ├── shared-utils/ ← pacote interno compartilhado
│ │ └── src/
│ └── ui-components/ ← biblioteca de componentes
│ └── src/
│
├── package.json ← workspace root (pnpm/yarn/npm workspaces)
└── turbo.json ← pipeline de build (Turborepo)
Variantes do monorepo
Três variantes principais se diferenciam pelo nível de tooling:
- Monorepo puro (estilo Google/Meta): um repositório único para literalmente todo o código da empresa. Requer ferramentas proprietárias (Bazel, Pants, Buck) para que o CI só recompile e reteste o que mudou. A escala exige investimento pesado em tooling.
- Monorepo com workspaces: o modelo mais comum fora do mundo das big techs. Ferramentas como Turborepo, Nx ou pnpm workspaces gerenciam as dependências entre pacotes internos, proveem cache incremental de build e permitem rodar tarefas apenas nos pacotes afetados por uma mudança.
- Multi-repo com pacotes publicados: variante do multi-repo onde as bibliotecas compartilhadas são publicadas num registry privado (npm private, Verdaccio, GitHub Packages) e consumidas como dependências versionadas. Mantém o isolamento de repositórios mas adiciona o custo de ciclos de publicação para propagação de mudanças.
Como funciona
Compartilhamento de código no monorepo
Com workspaces, um pacote interno é referenciado diretamente pelo caminho local — sem precisar publicar no npm a cada mudança:
// apps/api-gateway/package.json
{
"dependencies": {
"@org/shared-utils": "workspace:*"
}
}
// A ferramenta de workspace (pnpm, Turborepo, Nx) resolve
// "@org/shared-utils" para packages/shared-utils/ localmente.
// Nenhuma publicação é necessária para testar a mudança.
Cache incremental no monorepo
Ferramentas como Turborepo mantêm um grafo de dependências entre pacotes
e um hash do conteúdo de cada um. Quando apenas shared-utils
muda, somente os pacotes que dependem dele são recompilados e retestados.
Sem essa camada de cache, o CI de um monorepo grande retesta tudo a cada
push — tornando-o mais lento que o multi-repo equivalente.
Propagação de mudanças no multi-repo
No multi-repo, uma mudança numa biblioteca compartilhada requer: (1) commit
e push na biblioteca, (2) publicação de uma nova versão no registry, (3)
atualização do package.json em cada consumidor, (4) PR + CI em
cada repositório consumidor. Para uma mudança de breaking change que afeta
10 serviços, isso gera 10 PRs separados que precisam ser coordenados
manualmente.
Quando usar cada estratégia
Prefira monorepo quando:
- O código é fortemente interdependente: mudanças frequentes cruzam múltiplos projetos ou pacotes. Um monorepo permite PR único, revisão unificada e CI que valida tudo de uma vez.
- O time é pequeno a médio e trabalha nos mesmos projetos: a visibilidade total do código reduz silos e facilita refatorações transversais sem coordenação de repositórios.
- Padronização é prioritária: configuração de linter, formatter, TypeScript e CI pode ser centralizada uma vez e aplicada a todos os projetos automaticamente.
- Há muitas bibliotecas internas compartilhadas: o ciclo de desenvolvimento de libs internas sem a necessidade de publicação acelera dramaticamente a iteração.
Prefira multi-repo quando:
- Equipes têm autonomia total e ciclos de release independentes: times que nunca ou raramente compartilham código e que precisam de permissões de acesso diferentes se beneficiam do isolamento do multi-repo.
- Os projetos têm tecnologias muito distintas: um serviço em Rust, outro em Python e um frontend em TypeScript têm pouquíssimo a ganhar de um monorepo — o tooling compartilhado não se aplica.
- Há pacotes que serão publicados como open-source: bibliotecas destinadas à comunidade são mais naturalmente gerenciadas em repositórios próprios, com seus próprios issues, releases e mantenedores.
- O time não quer investir em tooling de monorepo: sem cache incremental, um monorepo grande punirá o CI com builds lentos. Se não há disposição para manter Turborepo, Nx ou similar, o multi-repo é mais simples de operar.
Prós e contras
Monorepo — Prós
- Mudanças atômicas: uma alteração que afeta múltiplos pacotes aparece num único commit e PR.
- Sem necessidade de publicação para iterar em bibliotecas internas — o workspace resolve localmente.
- Padronização de tooling, linting e CI centralizada num único lugar.
- Visibilidade total do código — qualquer desenvolvedor pode descobrir e reutilizar o que existe.
- Refatorações transversais (rename, assinatura de interface) são feitas em um único PR.
Monorepo — Contras
- CI mais lento sem cache incremental — sem tooling adequado, tudo é retestado a cada push.
- Repositório cresce indefinidamente; git clone e operações de histórico ficam pesados sem ferramentas como git sparse-checkout.
- Permissões de acesso são por repositório no Git — controle granular de quem acessa o quê exige workarounds (CODEOWNERS, branch protection).
- Acoplamento acidental é mais fácil: a barreira de fazer um import entre projetos é zero.
Multi-repo — Prós
- Isolamento total: um repositório não pode afetar outro por acidente.
- Permissões de acesso nativas por repositório.
- CI de cada repositório é simples e rápido — testa apenas o que está naquele repo.
- Ciclos de release completamente independentes.
Multi-repo — Contras
- Propagação de mudanças em libs compartilhadas exige múltiplos PRs coordenados manualmente.
- Duplicação de configuração (linter, CI, tsconfig) em cada repositório — divergências surgem com o tempo.
- Dificuldade de descoberta: o que já existe e pode ser reutilizado?
- Versioning hell: dependências internas com versões defasadas coexistindo em serviços diferentes.
Armadilhas comuns
1. Confundir monorepo com monolito
Esta é a confusão mais frequente — e mais custosa. Monorepo e monolito são eixos ortogonais. Monorepo é uma estratégia de versionamento de código; monolito é uma decisão de arquitetura de deployment.
As quatro combinações são completamente válidas:
- Monolito em monorepo: uma única aplicação deployada como um bloco, com todo o código num único repositório.
- Microsserviços em monorepo: múltiplos serviços independentemente deployáveis, todos versionados juntos (o modelo do Google).
- Monolito em multi-repo: uma única aplicação deployada como bloco, com seu código em repositório próprio.
- Microsserviços em multi-repo: cada serviço tem seu repositório independente (o modelo mais comum em adoções iniciais de microsserviços).
Atenção: mover de multi-repo para monorepo não distribui automaticamente um sistema monolítico. E adotar microsserviços não obriga multi-repo. São decisões independentes.
2. Monorepo sem cache incremental
Montar um monorepo juntando os projetos num único repositório sem configurar cache incremental de build e teste é a receita para um CI que cresce linearmente com o tamanho do repositório. Antes de migrar para monorepo, defina qual ferramenta de orquestração será usada (Turborepo, Nx, Bazel) e configure o pipeline incremental desde o início.
3. Acoplamento acidental via imports internos
No monorepo, a barreira técnica para importar de outro pacote é zero —
basta o caminho relativo funcionar. Sem enforcement de boundaries
(regras de lint como eslint-plugin-boundaries ou as
restrições de projeto do Nx), equipes inadvertidamente criam dependências
entre pacotes que deveriam ser independentes. O resultado é um monorepo
onde nada pode ser deployado isoladamente — o pior dos dois mundos.
4. Histórico gigante sem sparse-checkout
Em monorepos com anos de histórico e muitos arquivos binários, o
git clone pode levar minutos. A solução é usar
git clone --filter=blob:none --depth=1 (shallow + partial
clone) ou configurar sparse-checkout para que cada desenvolvedor obtenha
apenas os diretórios que precisa.
Tópicos relacionados
A decisão de monorepo vs multi-repo influencia como as equipes organizam o código internamente, mas não determina essa organização. Um monorepo com múltiplos projetos pode ter cada projeto organizado por feature ou por camada técnica — são decisões independentes que se complementam.
A distinção entre monolito e microsserviços é ortogonal à estratégia de repositório: a arquitetura de deployment define como os componentes rodam em produção, enquanto a estratégia de repositório define como o código é versionado e desenvolvido. Organizações que adotam microsserviços frequentemente migram para monorepo justamente para recuperar a visibilidade e a coordenação de mudanças que o multi-repo por serviço dificulta.