Boas práticas de arquitetura com NodeJS + Express

Workshop IFRS
Workshop IFRS

Quando trabalhamos com uma plataforma nova como o Node.js, é muito comum ficarmos em dúvida de como proceder com a escala de nossas aplicações e principalmente com a arquitetura da mesma, conforme vão crescendo.

Além disso, considerando os fundamentos do Node.js, o fato de trabalhar com Javascript e sua inerente dinamicidade e pouco apreço por tipagem, orientação à objetos e muito mais, torna a transição de programadores mais tradicionais (Java, C#, etc) para esta plataforma um tanto confusa.

Eu mesmo, tenho feito apenas pequenos projetos em Node.js pois ainda não me sinto confortável para construir (se é que devo) grandes projetos com ele.

Esse post é para compartilhar as melhores dicas e boas práticas de arquitetura que tenho encontrado na Internet sobre a construção de aplicações Node.js, especialmente usando o Express, o framework de aplicações web mais famoso para Node.

O quão grande é a sua aplicação?

Aplicações web não são sempre a mesma coisa e, da mesma forma, não há uma única arquitetura e/ou estrutura de código que possa ser aplicada à todas aplicações express.js.

Sim, é possível criar grandes aplicações em Node.js uma vez que grandes empresas estão fazendo exatamente isso nos últimos anos, como Walmart, Netflix e Uber, só para citar algumas. A questão aqui é: sua aplicação realmente é grande e precisa de uma arquitetura como a deles?

Se sua aplicação é pequena, você não precisa de uma estrutura complexa de diretórios como a que vou mostrar aqui. Mantenha sua aplicação simples, garanta que os arquivos mais importantes estarão na raiz do diretório e está feito.

Agora, se sua aplicação é gigantesca, em algum ponto você pode precisar quebra’-la em pacotes NPM separados (semelhante ao que fazemos com as class libraries em Java e C#). Em geral, o que se recomenda a fazer com Node.js é sempre trabalhar com pacotes pequenos, especialmente para bibliotecas, uma vez que o fato de usar a linguagem Javascript não ajuda muito em manter o controle e previsibilidade de funcionamento em uma aplicação conforme ela fica muito grande. Sendo assim, conforme sua aplicação for crescendo e partes do seu código comecem a se tornar reutilizáveis, mova-os para um pacote como se fosse um subsistemas ou biblioteca, tornando-o independente no NPM.

Com iso em mente, o restante deste artigo foca em dicas e princípios para construção de uma estrutura funcional boa para aplicações médias que contenham as seguintes características:

  • O site tem algumas páginas e templates estáticos
  • A parte dinâmica do site é desenvolvida usando um estilo SPA
  • A aplicação expõe uma API no estilo REST/JSON para o navegador consumir
  • Os modelos da aplicação são de um único domínio de negócio, como por exemplo um site de revenda de automóveis.

Dito isso, vamos às dicas.

Princípios e Motivações Principais

Acima de qualquer estrutura que eu possa propor aqui, vale ressaltar alguns princípios e motivações que devem estar acima de quaisquer convenções em seu código:

Ajude seu cérebro a entender o projeto. Conseguimos lidar com apenas pequenas quantidades de informação de cada vez. Por isso que usamos pastas. Por isso que criamos funções. Elas nos ajudam a lidar com a complexidade do sistema nos permitindo mexer em pequenas porções dele de cada vez.

Crie apenas os diretórios que precisa. Não saia criando dezenas de diretórios que acabarão vazios ou com apenas um arquivo dentro. Comece com o básico de pastas que você precisa e vá adicionando conforme a complexidade for aumentando. Afinal, você não compra um ônibus como primeiro veículo pensando no dia em que pode querer dar carona para muita gente, certo?

Torne fácil localizar arquivos de código. Os nomes de pasta devem ser significativos, óbvios e fáceis de entender. Código que não é mais usado, bem como arquivos antigos, devem ser removidos do projeto, e não apenas comentados ou renomeados. Além disso, todos os códigos ficam dentro de um diretório app na raiz e seus subdiretórios, para facilitar buscas posteriores, especialmente via linha de comando. Ah, e o padrão do npm é usar nomes sempre em minúsculo para as pastas, sem acentos, e com os espaços substituídos por hífens (-). Então é melhor seguir esta regra, por mais que não goste dela.

Agrupe por feature, não por responsabilidade. Esta é uma sugestão que vai contra o que a maioria de nós está acostumado, de separar os arquivos por responsabilidade (controller, model, etc). Cada feature da sua aplicação Node possui uma série de arquivos que, se estiverem juntos (em uma pasta por feature), fica mais fácil de dar manutenção depois. Ex: preciso adicionar um novo campo em um model, na mesma pasta eu já posso mexer no controller que usa aquele model.

Guarde seus testes perto do seu código. Essa ideia vai de encontro à anterior. Coloque o arquivo JS de testes de cada feature na mesma pasta da feature, para facilitar os testes e correção dos bugs encontrados. Também ajuda a lembrar das features que ainda não possuem testes codificados.

Para não se perder com os nomes de arquivos a sugestão é usar uma convenção de nome baseada em sufixo, por exemplo:

  • o arquivo JS original é foo.js
  • o arquivo de teste do original é foo.test.js

Diminua o acoplamento e aumente o reuso de software. A ideia com essas duas últimas dicas é tornar sua aplicação mais modular, com cada feature sendo um módulo mais independente. Aqui a ideia permanece. Uma vez que você tenha módulos independentes, sua aplicação fica menos acoplada e a manutenção mais simples. Uma dica para aumentar o reuso de software mantendo o acoplamento baixo é não colocar código “especializado” nas suas chamadas de model e controller. Ex: após salvar um cliente no banco você precisa enviar um email pra ele. Não coloque o código que envia email logo após o salvamento no banco. Ao invés disso, dispare uma função de um módulo de email.

Priorize configuration over convention. Não faça coisas mágicas como assumir que o nome de um arquivo é x ou que os uploads vão parar sempre em uma pasta y. Isso é frágil e facilmente quebra sua aplicação quando alguma coisa muda e você não lembra de todas as convenções que criou para sua arquitetura. Programe de maneira que o código em si possa fazer com que o programador entenda todo o projeto.

Convencione nomes de arquivos no formato ‘lower-kebab-case’. Use apenas minúsculas e ‘-‘ como separador entre palavras. Isso evita problemas de sistemas de arquivos em SOs diferentes que sua aplicação Node possa rodar. Ex: cadastro-cliente.js

Outras dicas úteis

Somente app/server.js deve carregar o módulo app/config.js. Assim, ele se encarrega de passar os objetos options menores para configurar os demais subsistemas ao invés de ficar todo mundo carregando um arquivo cheio de informações globais, causando acoplamento desnecessário.

Centralize a criação de conexões ao banco. Passe as conexões criadas para os subsistemas ao invés de ficar passando informações de conexão por parâmetro e permitindo que eles criem suas próprias conexões.

Centralize o acesso às variáveis de ambiente NODE_ENV, mais especificamente no app/config.js. Os demais locais da sua aplicação que necessitem de variáveis de ambiente devem esperá-las por parâmetro.

Para mais dicas e exemplos de projetos usando estes princípios, dê uma olhada nesse repositório do Github, que serviu de inspiração para esse post.

O que achou desse artigo?
[Total: 2 Média: 4.5]
  • Alexandre Maeda

    Valeu pelas dicas!