Monorepo vs Polyrepo, qual escolher ?
Minhas experiências lidando com essas duas formas de organizar código
Desde que comecei como engenheiro de software, trabalhar com polyrepo era o padrão para mim. Se eu precisasse criar um novo serviço, era só criar um novo repositório, configurar os pipelines de CI/CD e pronto. Simples de lidar, gerenciar e fazer o deploy. Mas quando comecei na Avenue, conheci uma nova maneira de organizar o código: monorepos.
A partir daí foram vários aprendizados, e gostaria de compartilhar com vocês as minhas percepções sobre ambas as formas.
Polyrepo
Para mim, é a forma mais prática de codar um serviço. É quase automático criar um novo repositório quando você precisa criar um serviço novo. Se precisar atualizar uma biblioteca, é mais fácil de testar, fazer o deploy e você se sente à vontade para refatorar qualquer parte do código. Além disso, é bem simples rodar um serviço isolado localmente, basta escrever um arquivo Docker Compose, Tiltfile e as dependências da aplicação estarão rodando e configuradas exatamente como você configurou. A palavra-chave aqui é atomicidade. Tudo o que você faz impacta apenas o serviço com o qual está lidando.
À medida que sua plataforma vai crescendo, novos repositórios, microserviços, bibliotecas internas serão criados. Esses microserviços terão conexões entre si, e é aí que os problemas começam. Clonar, fazer o build e rodar todos os microserviços localmente pode ser muito trabalhoso, e fica mais difícil para novos membros da equipe entenderem o que precisam fazer para ter o ambiente local funcionando completamente. Outro problema comum é manter os diversos serviços usando a versão mais recente das bibliotecas, e refatorá-los nesse sentido geralmente custa caro porque você precisa fazer cada refactor de forma atômica, um a um, para fazer as alterações necessárias.
Vamos imaginar um cenário onde temos uma biblioteca que detém os contratos que os serviços usam para se comunicar (situação muito comum quando se usa Protobuf) e você precisa alterar um contrato específico. Você precisa estar ciente de que mudanças desse tipo podem causar efeitos colaterais em todos os serviços relacionados. Como testar isso? Como medir quais serviços podem ser afetados? Como medir o impacto? Uma boa estratégia é ter uma suíte robusta e com uma boa cobertura de testes automatizados em várias camadas. Mas mesmo que você consiga fazer isso, eventualmente ainda precisará coordenar as alterações e os deploys dos serviços.
TL;DR
Um único repositório é mais fácil de se criar e gerenciar, mas quando você tem centenas de repositórios, fica mais difícil coordenar mudanças entre eles, e a complexidade para rodar sua aplicação localmente aumenta.
Monorepo
A primeira vez que vi um monorepo, pensei: Qual é a diferença entre monorepos e um monolito? A verdade é que são coisas completamente diferentes. A ideia por trás de um monorepo é ter toda a sua base de código no mesmo lugar, mas isso não significa que você não pode ter vários serviços dentro dele. Tudo se resume a forma como você organiza seu código e como você o estrutura. Um monorepo deve ser como um workspace, com diversos serviços e packages dentro dele. Tudo está no mesmo diretório, mas a fronteira dos serviços é clara e você não pode compartilhar código entre entre eles de qualquer forma. Na minha visão, um grande sinal de um monorepo bem estruturado é quando ele permite você ser capaz (se precisar) de dividi-lo em muitos polyrepos sem refatorar milhares de linhas de código (claro que na vida real não é tão fácil, mas é uma boa maneira de se pensar sobre isso). Em um monorepo bem estruturado é simples refatorar código. Se você quebrar algo, pode ver na hora o que não está mais funcionando e atuar em cima do problema com mais agilidade. Se você adicionar um novo comportamento em alguma biblioteca compartilhada, seus serviços automaticamente se comportarão como você deseja. É mais fácil gerenciar o codebase.
Ok, e as desvantagens? Quando você organiza sua base de código em um único repositório, inevitavelmente terá um grande trabalho para testar e fazer o deploy da aplicação. Você verá o tempo de build do seu pipeline de CI crescer exponencialmente. Mudanças pequenas em qualquer pedaço de código levarão mais tempo para serem testadas e implantadas. Além disso, geralmente nesse cenário muitos engenheiros trabalharão no mesmo repositório. A equipe deve ter um bom fluxo de trabalho para evitar que códigos mal testados localmente bloqueiem as releases ou criem problemas de merge/rebase.
Vale ressaltar que empresas como Google e Uber adotaram monorepos em suas equipes de engenharia, e como você pode imaginar, elas têm bases de código muito maiores do que as empresas normais. Elas usam ferramentas como Bazel e Buck para possibilitar testar e buildar apenas o que mudou no código, ajudando a trazer de volta a agilidade para os pipelines de CI/CD. Você não precisa gastar tempo rodando, testando/fazendo build para tudo o tempo todo, você testa e faz deploy apenas o que mudou. Mas essas ferramentas não são simples de adotar. Elas são um tanto quanto complexas de gerenciar, você precisa configurá-las e mantê-las. Além disso, tem o custo da curva de aprendizado, seu time vai precisar aprender a usar essas ferramentas, já que elas se tornarão uma ferramenta core para testar e buildar seus serviços.
TL;DR
Monorepos facilitam o compartilhamento de código entre projetos, facilitam o processo de refatoração de código e execução da sua plataforma localmente. Mas por outro lado, aumentam a complexidade do pipeline de CI/CD.
Híbrido
Pensando em um meio termo, você pode construir um estilo híbrido aproveitando o melhor dessas duas opções. Pode criar um repositório baseado em uma equipe ou uma plataforma e agrupar serviços no mesmo contexto. As pessoas ainda poderão compartilhar códigos comuns entre os serviços, mas não precisarão se preocupar quanto a quebrar o pipeline de outra pessoa ou time. Se sua equipe se esforçar para não deixar isso se tornar um monorepo, talvez você não tenha as desvantagens de CI/CD e não precise usar ferramentas como Bazel e Buck para melhorar o tempo de build. Você terá um tempo de build mais alto em comparação com um polyrepo? Provavelmente sim, mas o custo de adicionar uma ferramenta só pra gerenciar as builds versus apenas lidar com o tempo de build pode não valer a pena.
Conclusão
Não existe uma escolha certa. A equipe precisa descobrir o que se encaixa melhor. Ambos os lados têm benefícios e desvantagens, e a decisão sobre qual estratégia escolher pode ser apenas uma questão de quando. Em um momento em que a empresa está crescendo de forma rápida, uma estratégia de monorepo pode trazer várias vantagens para o time. Quando a equipe crescer, você pode mudar para um estilo híbrido ou polyrepo. É importante entender bem as razões por trás de cada estratégia, porque isso refletirá na produtividade da equipe, DevOps, onboarding de novas pessoas, na velocidade que as coisas vão para produção.
E no seu contexto, qual estratégia usam ? E qual você acha que é a ideal e por quê ? Sinta-se a vontade para falar nos comentários ou para me chamar pra gente bater um papo sobre :)