Práticas recomendadas Node.js - Dicas da estrutura do projeto


Olá Habr! Apresento a você a tradução adaptada do primeiro capítulo das " Melhores práticas do Node.js.", de Yoni Goldberg. Uma seleção de recomendações sobre o Node.js está publicada no github, tem quase 30 toneladas de estrelas, mas até agora não foi mencionada no Habré. Suponho que essas informações sejam úteis, pelo menos para iniciantes.

1. Dicas de estrutura do projeto


1.1 Estruture seu projeto por componente


O pior erro de grandes aplicações é a arquitetura monolítica na forma de uma enorme base de código com um grande número de dependências (código spaghetti), essa estrutura atrasa bastante o desenvolvimento, principalmente a introdução de novas funções. Dica - separe seu código em componentes separados. Para cada componente, selecione sua própria pasta para os módulos de componentes. É importante que cada módulo permaneça pequeno e simples. Na seção "Detalhes", você pode ver exemplos da estrutura correta dos projetos.

Caso contrário: será difícil para os desenvolvedores desenvolver o produto - adicionar novas funcionalidades e fazer alterações no código será lento e terá uma grande chance de quebrar outros componentes dependentes. Acredita-se que, se as unidades de negócios não estiverem divididas, poderão surgir problemas com a escala do aplicativo.

Detalhes
Explicação de um parágrafo

Para aplicações de tamanho médio e superior, os monólitos são realmente ruins - um programa grande com muitas dependências é simplesmente difícil de entender e muitas vezes leva ao código de espaguete. Mesmo programadores experientes que sabem como “preparar adequadamente os módulos” gastam muito esforço no design da arquitetura e tentam avaliar cuidadosamente as conseqüências de cada alteração nas relações entre os objetos. A melhor opção é uma arquitetura baseada em um conjunto de pequenos programas de componentes: divida o programa em componentes separados que não compartilham seus arquivos com ninguém, cada componente deve consistir em um pequeno número de módulos (por exemplo, módulos: API, serviço, acesso ao banco de dados, teste etc.), para que a estrutura e a composição do componente sejam óbvias. Alguns podem chamar essa arquitetura de "microsserviço", mas é importante entender que os microsserviços não são uma especificação que você deve seguir, mas um conjunto de alguns princípios. A seu pedido, você pode adotar ambos, princípios individuais e todos os princípios da arquitetura de microsserviço. Ambos os métodos são bons se você mantiver a complexidade do código baixa.

O mínimo que você precisa fazer é definir os limites entre os componentes: atribua uma pasta na raiz do seu projeto para cada um deles e torne-os independentes. O acesso à funcionalidade do componente deve ser implementado apenas por meio de uma interface pública ou API. Essa é a base para manter a simplicidade de seus componentes, evitar o “inferno de dependências” e permitir que seu aplicativo cresça para um microsserviço completo.

Citação do blog: "Dimensionar requer dimensionar todo o aplicativo"
No blog MartinFowler.com
Aplicativos monolíticos podem ser bem-sucedidos, mas as pessoas ficam cada vez mais frustradas com eles, principalmente quando pensam em implantar na nuvem. Qualquer alteração, mesmo que pequena, no aplicativo requer a montagem e a reimplantação de todo o monólito. Muitas vezes, é difícil manter constantemente uma boa estrutura modular na qual as alterações em um módulo não afetam outras. O dimensionamento requer o dimensionamento de todo o aplicativo, e não apenas de suas partes individuais, é claro, essa abordagem exige mais esforço.

Citação do blog: “Do que está falando a arquitetura do seu aplicativo?”
Do blog do tio-bob
... se você esteve na biblioteca, representa sua arquitetura: a entrada principal, balcões de recepção, salas de leitura, salas de conferências e muitos corredores com estantes de livros. A própria arquitetura dirá: este edifício é uma biblioteca.

Então, sobre o que está falando a arquitetura do seu aplicativo? Quando você olha para a estrutura de diretórios de nível superior e os arquivos do módulo, eles dizem: Eu sou uma loja online, sou contador, sou um sistema de gerenciamento de produção? Ou eles estão gritando: sou Rails, sou Spring / Hibernate, sou ASP?
(Nota do tradutor, Rails, Spring / Hibernate, ASP são estruturas e tecnologias da web).

Estrutura adequada do projeto com componentes autônomos



Estrutura incorreta do projeto com agrupamento de arquivos por finalidade



1.2 Separe as camadas de seus componentes e não as misture com a estrutura de dados Express


Cada um de seus componentes deve ter "camadas", por exemplo, para trabalhar com a web, lógica de negócios, acesso ao banco de dados, essas camadas devem ter seu próprio formato de dados, não misturado com o formato de dados de bibliotecas de terceiros. Isso não apenas separa claramente os problemas, mas também facilita muito a verificação e o teste do sistema. Geralmente, os desenvolvedores de API misturam camadas passando objetos da camada da web do Express (como req, res) para a lógica de negócios e a camada de dados - isso torna seu aplicativo dependente e fortemente conectado ao Express.

Caso contrário: para um aplicativo em que os objetos de camada são misturados, é mais difícil fornecer teste de código, organização de tarefas CRON e outras chamadas não Expressas.

Detalhes
Divida o código do componente em camadas: web, serviços e DAL



O outro lado é misturar camadas em uma animação gif



1.3 Embrulhe seus utilitários básicos em pacotes npm


Em um aplicativo grande que consiste em vários serviços com seus próprios repositórios, utilitários universais como logger, criptografia etc. devem ser agrupados com seu próprio código e apresentados como pacotes npm privados. Isso permite que você os compartilhe entre várias bases de código e projetos.

Caso contrário: você deve inventar sua própria bicicleta para compartilhar esse código entre bases de código separadas.

Detalhes
Explicação de um parágrafo

Assim que o projeto começar a crescer e você tiver componentes diferentes em servidores diferentes usando os mesmos utilitários, comece a gerenciar dependências. Como posso permitir que vários componentes o usem sem duplicar o código do seu utilitário entre repositórios? Existe uma ferramenta especial para isso, e é chamada npm .... Comece agrupando pacotes de utilitários de terceiros com seu próprio código para que você possa substituí-lo facilmente no futuro e publique esse código como um pacote npm privado. Agora toda a sua base de códigos pode importar o código dos utilitários e usar todos os recursos de gerenciamento de dependências npm. Lembre-se de que existem as seguintes maneiras de publicar pacotes npm para uso pessoal sem abri-los para acesso público: módulos privados , um registro privado ou pacotes npm locais .

Compartilhando seus próprios utilitários compartilhados em diferentes ambientes


1.4 Separar o Express em "aplicativo" e "servidor"


Evite o hábito desagradável de definir um aplicativo Express inteiro em um arquivo enorme, divida seu código 'Express' em pelo menos dois arquivos: uma declaração de API (app.js) e um código de servidor www. Para uma estrutura ainda melhor, coloque uma declaração de API nos módulos do componente.

Caso contrário: sua API estará disponível apenas para teste através de chamadas HTTP (que é mais lenta e muito mais difícil de gerar relatórios de cobertura). Ainda assim, suponho, não é muito divertido trabalhar com centenas de linhas de código em um arquivo.

Detalhes
Explicação de um parágrafo

Recomendamos o uso do gerador de aplicativos Express e sua abordagem para criar o banco de dados do aplicativo: a declaração da API é separada da configuração do servidor (dados da porta, protocolo etc.). Isso permite que você teste a API sem fazer chamadas de rede, o que acelera os testes e facilita a obtenção de métricas de cobertura de código. Também permite implantar de forma flexível a mesma API para diferentes configurações de rede do servidor. Como bônus, você também obtém uma melhor separação de responsabilidades e um código mais limpo.

Código de exemplo: declaração da API, deve estar no app.js
var app = express(); app.use(bodyParser.json()); app.use("/api/events", events.API); app.use("/api/forms", forms); 

Exemplo de código: parâmetros de rede do servidor, devem estar em / bin / www
 var app = require('../app'); var http = require('http'); /** *          Express. */ var port = normalizePort(process.env.PORT || '3000'); app.set('port', port); /** *  HTTP-. */ var server = http.createServer(app); 


Exemplo: testando nossa API usando o supertest (um pacote de teste popular)
 const app = express(); app.get('/user', function(req, res) { res.status(200).json({ name: 'tobi' }); }); request(app) .get('/user') .expect('Content-Type', /json/) .expect('Content-Length', '15') .expect(200) .end(function(err, res) { if (err) throw err; }); 


1.5 Use uma configuração hierárquica segura com base em variáveis ​​de ambiente


Uma configuração ideal deve fornecer:

(1) ler chaves do arquivo de configuração e das variáveis ​​de ambiente,
(2) guardar segredos fora do código do repositório,
(3) estrutura de dados hierárquica (e não plana) do arquivo de configuração para facilitar o trabalho com as configurações.

Existem vários pacotes que podem ajudar a implementar esses pontos, como: rc, nconf e config.

Caso contrário: o não cumprimento desses requisitos de configuração levará à interrupção do trabalho de um desenvolvedor individual e de toda a equipe.

Detalhes
Explicação de um parágrafo

Quando você lida com definições de configuração, muitas coisas podem ser irritantes e lentas:

1. A configuração de todos os parâmetros usando variáveis ​​de ambiente se torna muito entediante se você precisar digitar mais de 100 chaves (em vez de apenas corrigi-las no arquivo de configuração); no entanto, se a configuração for especificada apenas nos arquivos de configuração, isso pode ser inconveniente para o DevOps. Uma solução de configuração confiável deve combinar os dois métodos: arquivos de configuração e substituições de parâmetros de variáveis ​​de ambiente.

2. Se o arquivo de configuração for JSON "simples" (ou seja, todas as chaves forem gravadas como uma única lista), com um aumento no número de configurações, será difícil trabalhar com ele. Esse problema pode ser resolvido através da formação de estruturas aninhadas contendo grupos de chaves de acordo com as seções de configurações, ou seja, organize uma estrutura de dados JSON hierárquica (veja o exemplo abaixo). Existem bibliotecas que permitem armazenar essa configuração em vários arquivos e combinar os dados deles em tempo de execução.

3. Não é recomendável armazenar informações confidenciais (como uma senha do banco de dados) em arquivos de configuração, mas não existe uma solução conveniente e definitiva para onde e como armazenar essas informações. Algumas bibliotecas de configuração permitem criptografar arquivos de configuração, outras criptografam essas entradas durante as confirmações do git, ou você pode salvar parâmetros secretos nos arquivos e definir seus valores durante a implementação por meio de variáveis ​​de ambiente.

4. Alguns cenários de configuração avançados exigem que você insira chaves pela linha de comando (vargs) ou sincronize os dados de configuração por meio de um cache centralizado, como o Redis, para que vários servidores usem os mesmos dados.

Existem bibliotecas npm que o ajudarão na implementação da maioria dessas recomendações.Recomendamos que você dê uma olhada nas seguintes bibliotecas: rc , nconf e config .

Exemplo de código: a estrutura hierárquica ajuda a encontrar registros e trabalhar com arquivos de configuração volumosos

 { // Customer module configs "Customer": { "dbConfig": { "host": "localhost", "port": 5984, "dbName": "customers" }, "credit": { "initialLimit": 100, // Set low for development "initialDays": 1 } } } 

(Nota do tradutor, os comentários não podem ser usados ​​no arquivo JSON clássico. O exemplo acima é retirado da documentação da biblioteca de configuração, que adiciona funcionalidade para pré-limpar arquivos JSON dos comentários. Portanto, o exemplo é bastante funcional, no entanto, linters como ESLint com configurações padrão podem "Juro" em um formato semelhante).

Posfácio do tradutor:

  1. A descrição do projeto diz que a tradução para o russo já foi lançada, mas como não encontrei essa tradução, peguei o artigo.
  2. Se a tradução lhe parecer muito breve, tente expandir as informações detalhadas em cada seção.
  3. Lamentamos que as ilustrações não tenham sido traduzidas.

Source: https://habr.com/ru/post/pt454476/


All Articles