DEV Community

Cover image for Minha experiência migrando projetos para estrutura de Monorepo com lerna.js
Lucas de Medeiros
Lucas de Medeiros

Posted on

Minha experiência migrando projetos para estrutura de Monorepo com lerna.js

Recentemente, adotei a estrutura de Monorepo em um de meus projetos pessoais, o Game Slot. Alguns motivos que me levaram a tomar essa decisão envolvem:

  • o escopo bem fechado e definido, sendo uma aplicação React, armazenada em um repositório chamado game-slot, que se comunica com uma API feita em NodeJS, antes no repositório game-slot-api;
  • features que realizavam mudanças no backend e no frontend da aplicação agora podem ser resolvidas em uma única Pull Request para apenas um repositório;
  • maior praticidade de lidar com configurações e dependências.

A princípio, fazer isso me pareceu uma tarefa simples, mas fui me deparando com alguns aspectos que irei discorrer ao longo deste artigo que acabaram tornando o processo um pouquinho menos trivial.

Versionamento

Inicialmente, pensei em apenas copiar todo o código da API que existia no repositório game-slot-api e colar dentro de uma pasta backend/ no game-slot, repositório que consistiria o monorepo, e também mover o código React para uma pasta frontend/. Isso transformaria a estrutura do repo em algo parecido com isso:

root/
├── backend/
│   ├── .eslintrc
│   ├── package.json
│   └── ...
├── frontend/
│   ├── .eslintrc
│   ├── package.json
│   └── ...
├── .gitignore
└── README.md

Porém, simplesmente copiar e colar o código da API dentro do monorepo significaria perder todos os commits que já tinham sido feitos no game-slot-api e apenas criaria um commit cheio de alterações.

Você pode até pensar que a perda desse histórico pode não se tornar algo crítico ou até não ser algo relevante para se preocupar. Eu concordo que, para o meu caso, não iria ter tanto impacto, mas fazer isso definitivamente não é uma boa prática, principalmente se você toma essa decisão em um cenário em que vários projetos e pacotes que já estão em produção e sendo utilizados por uma quantidade considerável de usuários são afetados.

Incomodado com essa situação, pesquisei sobre alguma forma de manter o histórico de um repositório em um monorepo, e descobri que utilizar o lerna poderia tornar minha vida muito mais fácil, já que é uma ferramenta feita especificamente para gerenciar projetos em JavaScript feitos em múltiplos pacotes, como sua própria descrição diz. Um de seus comandos é o lerna import, que importa o projeto (ou pacote) de um repositório, preservando os autores, as mensagens e datas dos commits.

Então, iniciei um novo projeto lerna dentro da pasta raiz do repositório game-slot com npx lerna init, e rodei o seguinte comando:

npx lerna import ~/game-slot-api --dest=backend
Dependendo do tamanho e organização do projeto que você tentar importar, esse comando pode causar alguns problemas. Você pode acessar a documentação do comando import e verificar algumas flags adicionais que possam auxiliar no processo.

Isso gerou uma pasta packages/, que contém todos os pacotes do monorepo, e mandou para lá o novo pacote backend com todos os commits existentes no repositório antigo automagicamente! Depois, movi o código React já existente para packages/frontend. Também renomeei a pasta packages/ para game-slot/ e fiz as modificações necessárias no arquivo lerna.json e package.json a fim de poder chamar meus dois "pacotes" de @game-slot/frontend e @game-slot/backend.

Dependências

Agora eu tinha meus dois projetos importados dentro de um único repositório preservando os commits de todos, do jeito que eu queria! Mas depois, percebi que não estava tirando proveito de uma das melhores vantagens de se usar Monorepo: facilidade no gerenciamento de dependências.

Existiam várias dependências que eram compartilhadas entre os dois projetos, como o node-fetch, eslint, entre outras, que estavam nos arquivos package.json de ambos backend e frontend.

Depois de uma análise de dependências que apareciam nos dois pacotes e para cada uma encontrada, adicionei como dependência global do projeto, rodando o seguinte comando na pasta raiz:

npx lerna add <nome-do-pacote>
Novamente, para ter acesso a flags e opções a mais do comando add, verifique sua documentação

Isso me possibilitou algo que achei bastante vantajoso: usar a mesma versão de cada uma das dependências em ambos os projetos e uma maior praticidade para gerenciá-las futuramente. Lindo, né?

Deploys

Com os dois projetos configurados e com um fácil gerenciamento de dependências, agora foi a vez de resolver o problema do deploy, tanto do backend quanto do frontend. Eu já estava utilizando o Heroku para deploy da API do Game Slot e o Netlify para o sistema web com React, e acabei encontrando soluções pesquisando por casos similares de problemas que outras pessoas já passaram e resolveram.

  • Para o frontend, eu utilizei o guia do Netlify para configurações comuns de build para variados estilos de projeto, incluindo monorepo.
  • Já no backend, eu segui este tutorial para automatizar os deploys no Heroku em uma estrutura de monorepo, fazendo adaptações para o meu caso.

Fluxo de desenvolvimento

O fluxo de desenvolvimento se manteve quase o mesmo, qualquer pessoa pode criar uma issue e atribuí-la a mim ou a ela mesma para que seja resolvida. Então, é aberta uma PR que irá passar pelo processo de review. A única mudança é que agora o repositório também receberá issues e contribuições de backend também.

Para desenvolver, primeiramente deve-se configurar as variáveis de ambiente necessárias, explicadas no README de cada um dos projetos, e em seguida rodar os comandos abaixo para rodar backend e frontend:

yarn bootstrap # instalação de todos os pacotes e dependências do projeto
yarn start:backend # rodar o backend em modo de desenvolvimento
yarn start:frontend # rodar o frontend em modo de desenvolvimento

# ou opcionalmente

npm run bootstrap # instalação de todos os pacotes e dependências do projeto
npm run start:backend # rodar o backend em modo de desenvolvimento
npm run start:frontend # rodar o frontend em modo de desenvolvimento

Conclusão

Algumas motivações citadas que me levaram a aplicar Monorepo no meu projeto podem variar facilmente de um contexto para outro. Dando uma boa pesquisada, você vai encontrar argumentos a favor e contra a sua utilização, e também debates bem acalorados em fóruns e discussões por aí.

É bom citar que o meu caso consiste em um projeto pessoal de faculdade em que eu apliquei um pouco mais de esforço, e a única pessoa realmente responsável pelo desenvolvimento desse código sou eu, não existe um time. Ou seja, obviamente isso pode não funcionar em times com organizações e tamanhos diferentes. Adotar ou não esse padrão requer muito debate, e todos os integrantes do time devem ter espaço para opinar!

Dito isso, espero que tenham gostado desse artigo, e que tenha sido útil de alguma forma 😄.

Top comments (2)

Collapse
 
carlosspohr profile image
Carlos Spohr

Excelente artigo!

Estou em processo de migração para monorepo mas fiquei um pouco confuso (e até perdido) na questão da hierarquia dos projetos, como por exemplo um projeto utilitário e outro do frontend.
Na migração que fiz, com projetos do Next, os imports são por meio de caminhos relativos (../../app1/Bla.ts) e pensei que ficaria algo como 'import Bla from '@mono/app1' ...sabe de tem uma forma de poder usar @ nos imports?

Outra coisa foi também a questão de o projeto utilitário poder importar itens do projeto do frontend e vice-versa..sabe se existe algo para prevenir isso?

Collapse
 
thayannevls profile image
Thayanne Luiza

👏👏👏