server.js node.js - trabalhe com erros. Parte 1

Boa tarde

Este artigo é destinado a desenvolvedores familiarizados com o node.js.

Recentemente, eu estava preparando material sobre fatos úteis para desenvolvedores no node.js em nosso escritório. Os projetos em que estamos trabalhando são serviços de API que usam o módulo node.js express como um servidor da web. O material é baseado em casos reais em que o código funcionou incorretamente ou a lógica nele foi cuidadosamente oculta ou provocou erros durante a expansão. Com base nesse material, foi realizado um workshop de desenvolvimento de equipe.

Então, eu decidi compartilhar. Até agora, apenas a primeira parte é de cerca de 30%. Se estiver interessado, será continuado!

Tentei oferecer uma oportunidade para uma rápida familiarização, por isso ocultei exemplos, raciocínios e comentários nos spoilers. Se as afirmações forem óbvias, você pode pular a "água". Embora o nosso "rake" nos spoilers também possa ser interessante.

Um colega durante o seminário me fez uma pergunta, por que falar sobre isso, se tudo já está nesta ou naquela documentação? Minha resposta foi a seguinte. Apesar de a mensagem ser verdadeira, tudo está realmente na documentação, ainda cometemos erros irritantes relacionados a mal-entendidos ou ignorância das coisas básicas.

Vamos começar!

Máquina virtual Node.js



Rosqueamento único



Diferente do javavm, o nodejs-vm é de thread único ** .



Fonte

mais detalhes
Ao mesmo tempo, há um conjunto de encadeamentos auxiliares usados ​​pela própria máquina virtual, por exemplo, para organizar E / S. Mas todo o código do usuário é executado em apenas um segmento "principal".

Isso simplifica muito a vida, pois não há competição. A execução do código não pode ser interrompida em um local arbitrário e continuada em outro. O código é simplesmente executado até que seja necessário aguardar algo, por exemplo, disponibilidade de dados ao ler de um arquivo. Enquanto aguarda, outro manipulador pode ser executado, até que termine de trabalhar ou até que também comece a esperar por algo.

Ou seja, se houver uma estrutura de dados interna, não será necessário se preocupar em sincronizar o acesso a ela!

O que fazer se o segmento "principal" não tiver tempo para processar os dados?

O dimensionamento é feito iniciando outro processo node.js ou, se os recursos do servidor estão chegando ao fim, iniciando outro servidor.

consequências e nosso "ancinho"
Tudo está claro aqui também. Você sempre deve estar preparado para o fato de que pode haver (e provavelmente haverá) mais de um processo node.js. E às vezes também pode haver vários servidores.

O "rake" que foi oculto é encontrado em nosso código


Linhas paralelas se cruzam no infinito. É impossível provar, mas eu vi.
Jean Effel, "O romance de Adão e Eva".
Foi feita uma tentativa de garantir a exclusividade das instâncias de entidade no banco de dados exclusivamente pelo aplicativo. Em geral, isso e isoladamente do contexto não parece muito ruim , mas nessa situação ainda mais. Sem contratar um serviço de terceiros, essa tarefa me parece não ter solução.

O colega que estava envolvido nisso realmente queria implementar isso sem envolver o banco de dados real. No final, depois de algumas "abordagens ao projétil", isso foi realizado ... envolvendo o SharePoint.


** Multithreading ou "se você realmente quiser"



A partir da versão 10.5.0, o node.js possui suporte experimental para multithreading .


Fonte

Mas o paradigma permanece o mesmo
  • Cada novo fluxo de trabalho cria sua própria instância isolada do ambiente da máquina virtual node.js.
  • Os fluxos de trabalho não possuem dados mutáveis ​​comuns. (Existem algumas reservas, mas basicamente a afirmação é justa.)
  • A comunicação é feita usando mensagens e SharedArrayBuffer.

Portanto, o código antigo continuará funcionando ao usar fluxos de trabalho.
Leia mais aqui.


Ciclo de vida do aplicativo



O coração do nodejs-vm é o loop de eventos. Quando a execução do código deve ser suspensa ou o código, ao que parece, terminou, o controle passa para ele.

Texto oculto
O loop de eventos verifica se (oh) os eventos para os quais registramos os manipuladores ocorreram. Se algo acontecer, os manipuladores serão chamados. Caso contrário, será verificado se existem "geradores" de eventos para os quais registramos manipuladores. Uma conexão TCP ou temporizador aberto pode ser um desses geradores. Se eles não puderam ser encontrados, o programa é encerrado. Caso contrário, um desses eventos é esperado, os manipuladores são chamados e tudo se repete.

A conseqüência desse comportamento é o fato de que, quando o código parece ter terminado, a saída do nodejs-vm não ocorre, por exemplo, porque registramos um manipulador de timer, que deve ser chamado depois de algum tempo.

Isso é mostrado no exemplo a seguir.

console.log('registering timer callbacks'); setTimeout( function() { console.log('Timer Event 1'); }, 1000); console.log('Is it the end?'); 


resultado:
 registering timer callbacks Is it the end? Timer Event 1 


Leia mais aqui.

Outro "rake" em nosso código



Todos podem gerenciar o estado!
O sinal de se o usuário é um administrador foi armazenado em uma variável global. Essa variável foi inicializada como false no início do programa. Mais tarde, quando o administrador se registrou, essa variável foi configurada como true.

Como resultado, se o administrador visitou o sistema, qualquer usuário que acessou essa instância do serviço foi percebido como administrador.

Custou-me algum esforço para mostrar ao meu colega que havia um erro de lógica. Um colega tinha certeza de que para cada solicitação de HTTP é criado um ambiente completamente novo.


package.json - campos que valem a pena preencher



package.json é o arquivo de descrição para o nosso pacote. Nesse contexto, trata-se de nossa aplicação, e não de dependências. Os campos e explicações listados abaixo são por que vale a pena preenchê-los da mesma forma.

Texto oculto

nome


Até publicarmos o pacote no repositório, o campo também poderá ser pontuado. A questão é que esse campo seja conveniente para nomear o arquivo de instalação ou, por exemplo, para exibir o nome do produto em sua página da web. Em geral, "o que você chama de iate?"

versão


A idéia principal é não esquecer de aumentar o número da versão enquanto expande a funcionalidade, corrigindo bugs ... Infelizmente, em nosso escritório, você ainda pode encontrar produtos com a versão inalterada 0.0.0. E então adivinhe que tipo de funcionalidade funciona para o cliente ...

principal


Este campo informa qual arquivo será iniciado quando o aplicativo for iniciado (`npm start`). Se o pacote for usado como uma dependência, qual arquivo será importado ao usar nosso módulo por outro aplicativo. O diretório atual é o diretório em que o arquivo `package.json` está localizado.

E também, se, por exemplo, usarmos vscode , o arquivo especificado neste campo será iniciado quando o depurador for chamado ou quando o comando "execute" for executado.

A extensão ".js" pode ser omitida. É uma conseqüência de todos os casos de uso possíveis, portanto, não é explicitada diretamente na documentação.

motores


Este campo contém a tupla: {"nó": versão , "npm": versão , ...}.

Conheço os campos "nó" e "npm". Eles determinam as versões do node.js e do npm necessárias para o nosso aplicativo funcionar. As versões são verificadas executando o comando npm install.

A sintaxe padrão para determinar a versão dos pacotes de dependência é suportada: sem um prefixo (versão única), o prefixo "~" (os dois primeiros números da versão devem corresponder) e o prefixo "^" (somente o primeiro número da versão deve corresponder). Se houver um prefixo, a versão deverá ser maior ou igual à especificada neste campo. Apenas uma lista de versões; indicação explícita mais, menos, ... etc. também funciona.

Isenção de responsabilidade "Npm install" verifica as versões especificadas nos "mecanismos" apenas se o modo "rigoroso do mecanismo" estiver ativado. Nós o incluímos para cada projeto, adicionando o arquivo .npmrc com a linha: "engine-strict = true". Era uma vez, “npm install” fazia essa verificação por padrão.

Alguns contêineres, pelo menos na documentação, escrevem que as versões adequadas serão usadas por padrão. Nesse caso, estamos falando do Azure.

Um exemplo:
 "engines": { "node": "~8.11", // require node version 8.11.* starting from 8.11.0 "npm": "^6.0.1" // require npm version 6.* starting from 6.0.1 }, 


"ancinho" regular



E o rei está nu!

Foi acordado repetidamente com o cliente que a versão exigida do `node.js` deveria ser pelo menos 8. Quando as versões iniciais do aplicativo foram entregues, tudo funcionou. "Um dia" após a entrega da nova versão no cliente, o aplicativo parou de ser executado. Tudo funcionou em nossos testes.

O problema era que, nesta versão, começamos a usar a funcionalidade suportada apenas na versão 8 node.js. O campo "engines" não estava preenchido; portanto, ninguém havia notado antes que o cliente tinha uma versão antiga do node.js. (Serviços da Web do Azure padrão).

scripts


O campo contém uma tupla do formulário: {"script1": script1 , "script2": script2 , ...}.

Existem scripts padrão que são executados em uma determinada situação. Por exemplo, o script "install" será executado após a execução do "npm install". É muito conveniente, por exemplo, verificar a disponibilidade dos programas necessários para o aplicativo funcionar. Ou, digamos, para compactar todos os arquivos estáticos disponíveis no nosso serviço da Web, para que eles não precisem ser compactados em tempo real.

Nesse caso, você não pode se limitar apenas aos nomes padrão. Para executar um script arbitrário, você precisa executar "npm run script-name ".

É conveniente coletar todos os scripts usados ​​em um só lugar.

Um exemplo:
  "scripts": { "install": "node scripts/install-extras", "start": "node src/well/hidden/main/server extra_param_1 extra_param_2", "another-script": "node scripts/another-script" } 


PS A extensão ".js" pode ser omitida na maioria dos casos.


package-lock.json - ajuda a instalar versões específicas de dependências, não as "mais recentes"



Texto oculto
Git ou não git? ..


Este arquivo apareceu no npm relativamente recentemente. Seu objetivo é organizar a repetibilidade da montagem.

e mais um "ancinho"


Mas não mudei nada no meu programa! Ontem ela trabalhou!


Em uma máquina par, o aplicativo funcionou muito bem. Em outro computador em um ambiente idêntico, em um aplicativo colocado do git em um novo diretório, após a execução de 'npm install', 'npm start', erros até então inéditos apareceram.

O problema foi causado pelo fato de o arquivo 'package-lock.json' estar ausente no repositório git. Portanto, durante a instalação dos pacotes, todas as dependências do segundo ou mais níveis (naturalmente, não gravadas no pacote.json) foram instaladas o mais atual possível. No computador de um colega, estava tudo bem. Um conjunto incompatível de versões foi selecionado no computador testado.

package-lock.json - para o git!



Retornando da digressão. O arquivo 'package-lock.json' contém uma lista de todos os módulos instalados localmente para o nosso aplicativo. A presença desse arquivo permite recriar um conjunto individual de versões de módulos.

Resumo: não esqueça de colocar o git e incluí-lo no arquivo de entrega (instalação) do aplicativo!

Útil: se o arquivo 'package-lock.json' estiver ausente, mas houver um diretório 'node_modules' com todos os módulos necessários, o arquivo 'package-lock.json' poderá ser recriado:
 npm shrinkwrap rename npm-shrinkwrap.json package-lock.json 



Você pode acabar com isso por enquanto. As informações não incluídas são a técnica de simplificação de código usada por nossa equipe.

Se forem detectados erros, tentarei corrigi-los rapidamente!

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


All Articles