Repositórios de código

Tradicionalmente o desenvolvimento na plataforma nginstack ocorre na IDE do Engine utilizando a Virtual File System, sistema de arquivos onde os dados dos diretórios e arquivos são gravados no banco de dados e distribuídos automaticamente para todos os Engines.

Para pequenas customizações o uso da Virtual File System é adequado, no entanto essa solução apresenta as seguintes limitações para o desenvolvimento de um produto mais complexo:

  • Não há um ambiente isolado por desenvolvedor. As alterações são imediatamente replicadas para todos os usuários de uma base de dados. Uma alteração equivocada pode interromper o funcionamento do sistema para todos os usuários da base de dados.
  • Não há controle de alteração e revisão. Todas as alterações realizadas potencialmente podem ser enviadas para outras bases de dados sem que elas tenham sido revistas, pois ao utilizar o processo Atualizar sistema não é possível saber se há códigos em desenvolvimento ou não. Os desenvolvedores acabam tendo que atualizar pontualmente as demais bases utilizando o processo Atualizar VFS a fim de garantir que sejam enviadas apenas as alterações concluídas e testadas. Esse é um processo manual, que eventualmente exige um merge de alterações e é passível de falhas.
  • A IDE faz sugestão de preenchimento de código com base em uma configuração pré-cadastrada de sugestões. Ela funciona para as APIs nativas do sistema, mas não para códigos privados ou bibliotecas customizadas.
  • A IDE não possui ferramentas de análise e validação estáticas modernas, como o ESLint.

Por causa das limitações apresentadas acima, criou-se um modelo alternativo de desenvolvimento na plataforma fazendo uso de repositórios de códigos externos que são empacotados em arquivos especiais do sistema chamados de arquivos JAZ.

Os arquivos JAZ contém diretórios e arquivos que ao serem carregados pelo Engine são acessíveis por meio da API da Union File System. Esse é um sistema de arquivos alternativo do Engine que unifica todos os diretórios e arquivos de todos os arquivos JAZ contidos na Virtual File System.

Os arquivos contidos em um JAZ não podem ser modificados pela IDE do Engine. Para alterá-los é necessário criar um novo arquivo JAZ com o conteúdo do arquivo alterado. Os arquivos JAZ utilizam o mesmo formato dos arquivos ZIP, portanto eles podem ser descomprimidos e recriados utilizando qualquer ferramenta que trate arquivos ZIP. É recomendado que os arquivos JAZ sejam criados por processos de construção automatizados a partir de repositórios de códigos com controle de versão.

A documentação a seguir apresenta o modelo internamente adotado pela equipe de desenvolvimento do nginstack. As convenções adotadas nele não são obrigatórias, devendo ser observadas apenas como uma sugestão de configuração e fluxo de trabalho.

Convenções

JavaScript

São adotadas as seguintes convenções:

  • Strings devem ser preferencialmente iniciadas com aspas simples. O uso de aspas duplas em expressões SQL é uma exceção comum devido ao uso de aspas simples nas strings literais do SQL.
  • Não devem ser utilizadas strings com mais de uma linha. Elas devem ser concatenadas pelo operador “+”.
  • Indentação de 2 espaços.
  • Recuo de continuação de 4 espaços.
  • Atributos HTML textuais devem utilizar aspas duplas.
  • Em um protótipo ou classe, as propriedades devem ser preferencialmente declaradas antes dos métodos.
  • Parâmetros obrigatórios devem ser definidos antes dos parâmetros opcionais.
  • Não devem ser construídas instâncias de String, Number e Boolean. Sempre devem ser utilizadas as versões primitivas desses objetos.
  • Não devem ser utilizadas chaves literais nos códigos. Elas devem ser declaradas em constantes ou coleções de chaves, preferencialmente no diretório /keys.
  • Por convenção, o nome dos arquivos devem seguir a mesma convenção de nome adotada para o tipo exportado pelo módulo. Módulos que exportem construtores, classes, singletons, biblioteca de funções ou objeto literal devem adotar o PascalCase. Módulos que exportem funções devem adotar o camelCase.
  • Limite de 120 colunas por linha. Deve-se buscar evitar linhas com mais de 100 colunas com o objetivo de tornar mais produtivo os merges e revisões de códigos.

Documentação

JSDoc

Todos as APIs, sejam elas públicas ou privadas, devem ser documentadas no padrão JSDoc3.

O arquivo publicModules.js deve incluir todos os arquivos contendo APIs públicas que terão a sua documentação exportada.

A tag @author é utilizada exclusivamente para atribuir a autoria de códigos de terceiros embarcados na solução. Ela nunca deve ser utilizada para indicar profissionais da própria equipe de desenvolvimento.

Completion proposal

As APIs públicas precisam ser publicadas na configuração do Completion Proposal da IDE do Engine. A configuração fica localizada em /Configuração/Completion Proposal.

Manuais ou guias

Sempre que possível devemos utilizar o formato Markdown. Os arquivos devem ser formatados para possuir 120 colunas ou menos, simplificando a revisão no GitHub. As exceções são links ou trechos de código fonte, que não podem ser quebrados em várias linhas.

Estrutura de um repositório

É utilizado o Git como sistema de controle de versão de códigos e a gestão de dependências é realizada via yarn. Todos os pacotes JAZ e JavaScript privados são controlados em um único repositório. É utilizada a ferramenta Lerna para simplificar a gestão do repositório com múltiplos pacotes. A abordagem de um único repositório com vários pacotes não é obrigatória, mas traz benefícios na gestão das dependências e das ferramentas de de desenvolvimento auxiliares. A documentação a seguir levará em conta o uso dessas ferramentas e dessa abordagem.

Estrutura de arquivos

O repositório adota a seguinte estrutura de diretórios e arquivos raiz:

/repository-name
├── dictionaries: arquivos com palavras não reconhecidas pelo corretor ortográfico cSpell.  
├── packages: pacotes gerenciados neste repositório. Cada 
├── .editorconfig: configuração de estilo a ser adotado pelo editor/IDE. 
├── snippets: arquivos privados do desenvolvedor que não são versionados no repositório.
├── .env.example: exemplo contendo as variáveis de ambiente que o desenvolvedor 
     deve configurar no arquivo .env.
├── .env: configurações de ambiente informadas pelo desenvolvedor. 
├── package.json: configuração do mono repositório. Deve declarar as dependências de 
    desenvolvimento para todos os pacotes gerenciados por este repositório. 
├── .eslintrc.js: configuração do ESLint que será adotada para todos os pacotes 
     contidos neste repositório. 
├── lerna.json: configuração da ferramenta Lerna. Deve indicar a versão base dos 
     pacotes contidos neste repositório. 

Cara subdiretório de /packages representa um pacote JAZ ou Node e possui a estrutura abaixo:

/package-name
├── lib: classes, funções, enumerados, singletons e constantes de uso genérico.  
├── keys: coleções de constantes de chaves do sistema utilizados pelos demais códigos 
    do repositório. 
├── controllers: controladoras das APIs HTTP. 
├── routes: rotas das APIs HTTP.
├── scripts: scripts javaScript que não são módulos CommonJS, como scripts de scheduler,
├── startups: scripts de inicialização do Engine ou da sessão.
├── resources: arquivos de imagem, estilo, fontes incluídos como recursos estáticos. 
├── package.json: definição do pacote JAZ ou Node. Ao menos as propriedades name e version 
    devem ser preenchidas. 
├── jaz.config.json: configuração indicando os arquivos que farão parte do pacote JAZ. 
├── publicModules.js: relação de módulos que contém APIs públicas que podem ser utilizadas 
    fora do contexto deste pacote. 

As dependências dos pacotes que também estejam contidas no repositório devem ser declaradas por meio de um link "file://../package-name".

Módulos CommonJS

Os arquivos devem adotar o formato de módulo no padrão CommonJS fazendo uso do require para importar as dependências e module.exports para exportar o módulo definido no arquivo. No exemplo abaixo, foi declarado um módulo que cria um DataSet temporário vazio, sem definição de campos:

var DataSet = require('@nginstack/engine/lib/dataset/DataSet');

module.exports = function newDataSet() {
  return new DataSet();
}

Via de regra, não devem ser importadas dependências utilizando __include ou __includeOnce, nem devem ser utilizadas variáveis globais definidas fora do contexto do módulo. Todas as dependências de um módulo devem ser explicitamente declaradas, inclusive as APIs específicas e nativas do Engine. Essa abordagem garante que as dependências tenham sido de fato importadas e possibilita que o editor ou a IDE façam a sugestão e validação da dependência corretamente.

Arquivos que não possam ser representados como módulos devem ser criados no diretório scripts. Scripts de inicialização do Engine e de sessão devem preferencialmente serem declarados como módulos que exportam uma função e devem estar armazenados no diretório startups.

Pacotes JAZ

Um arquivo JAZ que contiver o arquivo package.json na sua raiz será tratado como um pacote pelo Engine. Ao carregar um pacote JAZ o Engine importará os diretórios e arquivos do pacote no diretório engine_modules em vez da raiz da Union File System.

O uso de pacotes JAZ simplifica a importação das dependências, pois permite que os módulos possam ser carregados via require utilizando a identificação do pacote. O Engine trata o diretório engine_modules de forma similar ao tratamento dado pelo Node.js ao diretório node_modules. Essa similaridade de uso permite que as IDEs reconheçam os módulos importados corretamente e possam fazer sugestões ou validações de uso. Exemplo: dado o pacote my-package contendo o arquivo /lib/myFunc.js, o arquivo será acessível no caminho /engine_modules/my-package/lib/myFunc.js da Union File System. Dessa forma, o módulo poderá ser importado utilizando var myFunc = require('my-package/lib/myFunc');

Importante: arquivos do pacote que não utilizem o formato de módulo devem ser importados via __include e __includeOnce utilizando o caminho /engine_module/my-package/path-to-file.

Dependências JAZ

Apesar dos pacotes JAZ serem instalados em engine_modules e serem de uso exclusivo do Engine, eles também precisam estar instalados em node_modules para que as IDEs e ferramentas de validação JavaScript possam realizar sugestões e validações das APIs.

Algumas funcionalidades da IDE também requerem que o pacote esteja declarado como dependência em dependencies ou em devDependencies. Pacotes JAZ também são pacotes Node válidos, mas por conterem códigos-fontes privados eles não podem ser publicados diretamente no registro público do npm. Algumas alternativas:

  • eles podem ser publicados sem os fontes no registro público do npm para que a dependência possa ser instalada utilizando o comando yarn.
  • pode ser utilizado o serviço pago de pacotes privados do npm.
  • pode ser configurado um serviço de registro de pacotes privado em vez do npm.

No desenvolvimento do nginstack adotou-se a primeira alternativa e por esse motivo os pacotes JAZ do nginstack também estão disponíveis no registro público do npm sem os seus códigos fontes. Para obter o conteúdo dos pacotes, deve ser utilizado o utilitário installJazDependencies do pacote @nginstack/build-tools.

Arquivos não versionados

O diretório snippets é uma área privada de testes do desenvolvedor e o arquivo .env possivelmente contém credenciais de acesso à base de dados. Por esse motivo, eles não devem ser versionados e devem ser incluídos no arquivo .gitignore.

Caso utilize as ferramentas de construção e testes disponibilizada pelo pacote @nginstack/build-tools, também devem ser ignorados os diretórios build e .engine utilizados para construir os artefatos do build.

Dessa forma, o arquivo .gitignore deve conter ao menos as seguintes exceções:

node_modules
build/
snippets/
.engine
.env

Caso o repositório contenha dados privados que não podem ser publicados, recomenda-se a criação do arquivo .npmignore com o conteúdo:

*

Limitações

O uso dos pacotes JAZ ainda não é adequado para as definições de classes, pois a manutenção do esquema do banco de dados requer que as definições sejam compartilhadas por todos os usuários do banco.

Os processos e relatórios também precisam ser criados na Virtual File System, pois as chaves deles são importantes para o modelo de permissões do sistema. Uma alternativa para permitir o versionamento do código-fonte dos processos e relatórios é utilizar os arquivos da Virtual File System apenas para incluir um arquivo contido em um pacote JAZ.

Essas limitações deverão ser revistas no futuro com a evolução do uso dos repositórios de códigos.

Configuração do repositório

Cada desenvolvedor deverá criar uma cópia local do repositório no computador e deverá configurar um Engine para utilizar a sua versão local do repositório. Dessa forma, apenas o Engine utilizado pelo desenvolvedor fará uso dos códigos alterados no repositório local e suas alterações não impactarão os demais desenvolvedores.

Pré-requisitos

Para a edição dos arquivos do repositório, é recomendada a instalação das seguintes ferramentas:

Outras IDEs ou clientes Git podem ser utilizados, mas a documentação do processo de desenvolvimento e eventuais ferramentas de desenvolvimento do nginstack focarão no uso delas.

Ativando o repositório local

Para que o Engine utilize o repositório local do desenvolvedor em vez do pacote JAZ gravado na Virtual File System é necessário que o repositório local seja incluído na Union File System. Para isso, deve ser adicionada uma configuração em Configuration/Others do Manage indicando os caminhos dos pacotes a serem lidos. Exemplo:

<UnionFS>
Directory c:\workspace\my-repository\packages\erp-core
</UnionFS>

Alternativamente, pode ser informado o diretório por meio de uma opção na linha de comando. Sintaxe: --ufsDirectory="c:\workspace\my-repository\packages\erp-core".

Nos dois modos de configurações, podem ser informados mais de um repositório caso seja necessário. Caso o diretório esteja em uma unidade de rede, deverá ser utilizado o caminho UNC. Exemplo: \ \vmware-host\Shared Folders\erp-core\ufs. Não utilize repositórios em unidades mapeadas do Windows.

Além de configurar os pacotes em disco a serem lidos, é necessário indicar ao Engine que ele está autorizado a permitir arquivos externos serem carregados na Union File System. Para isso, adicione o código abaixo em um script do diretório /Configuração/Inicialização do Engine.

// Script que habilita a configuração local da UnionFS
// Essa configuração somente deve ser feita em bases de Desenvolvimento
if (ngin && ngin.ufs && ngin.ufs.configureWithLocalSettings && engine &&
    engine.unionFS) {
  ngin.ufs.configureWithLocalSettings(engine.unionFS);
}

Essa configuração precisa ser realizada uma única vez em uma base de dados. Por isso, antes de adicionar a configuração acima, verifique se um outro desenvolvedor já não havia a adicionado antes.

Importante: a configuração acima deve ser habilitada apenas em bases de desenvolvimento. Essa configuração não deve ser habilitada em bases de produção.

Instalando as dependências

As dependências do pacote devem ser instaladas por meio do comando yarn na raiz do repositório. Para que as dependências JAZ sejam instaladas, é necessário que o arquivo .env esteja configurado com as credenciais de acesso do usuário.

Editores

Qualquer editor ou IDE pode ser utilizada para alterar os códigos, desde que ele utilize:

  • codificação UTF-8;
  • caracteres de espaço no lugar do tab (soft tabs);
  • indentação de 2 espaços.

Caso a IDE suporte, configure:

  • a margem direita para 100 colunas. O limite é 120, mas é importante que os códigos sejam horizontalmente mais concisos para facilitar a revisão das alterações.

Visual Studio Code

Caso utilize o Visual Studio Code, são recomendadas as seguintes extensões:

Por ser a IDE recomendada no desenvolvimento, algumas configurações para essa IDE são versionadas no repositório Git a fim de tornar mais prático o seu uso. Essas configurações estão contidas no diretório .vscode e podem ser sobrepostas pelas configurações de usuário se assim for desejado pelo desenvolvedor.

ESLint

Todos os códigos JavaScript devem ser validados pelo ESLint seguindo as configurações declaradas nos .eslintrc.js. Se suportado, ative a validação automática do ESLint na IDE.

Build

Após concluir o desenvolvimento, um repositório em disco precisa ser empacotado em um arquivo JAZ utilizando o formato ZIP. O arquivo pode ser criado por qualquer ferramenta de compressão que suporte o formato ZIP.

Ao criar o arquivo JAZ, não devem ser incluídos arquivos ou diretórios privados ou com informações potencialmente sigilosas, como o diretório snippets ou o arquivo .env.

@nginstack/build-tools

O pacote @nginstack/build-tools possui diversos scripts que auxiliam o processo de construção de um arquivo JAZ a partir de um repositório em disco. Segue uma sugestão de package.json fazendo uso desses scripts.

{
  "name": "my-package",
  "version": "1.0.0",
  "scripts": {
    "postinstall": "installJazDependencies",
    "build": "packJaz jaz.config.json",
    "test": "runTests \"lib,controllers,routes,adapters\" build/server-tests.xml",
    "deploy": "uploadToVfs build/jaz/my-package.jaz <JAZ_FILE_KEY>",
    "lint": "eslint --cache ."
  },
  "devDependencies": {
    "eslint": "^5.12.0"
  },
  "dependencies": {
    "@nginstack/build-tools": "^22.0.0",
    "@nginstack/engine": "^22.0.0",
    "@nginstack/web-framework": "^22.0.0"
  },
  "jazDependencies": [
    "@nginstack/engine",
    "@nginstack/web-framework"
  ]  
}

Exemplo de um arquivo de configuração jaz.config.json:

{
  "dest": "../../build/my-package.jaz",
  "files": [
    "package.json", "publicModules.js", "lib/**", "routes/**", "controllers/**", "keys/**", 
    "scripts/**", "startups/**", "resources/**", "adapters/**"
  ]
}

O uso do build-tools requer que as variáveis de ambiente ENGINE_URL, ENGINE_DATABASE, ENGINE_USERNAME, ENGINE_PASSWORD e DEVOPS_API_KEY estejam definidas. Alternativamente, pode ser criado o arquivo .env na raiz do repositório para definir essas variáveis. Exemplo de um arquivo .env:

DEVOPS_API_KEY=api-key-configured-in-my-database
ENGINE_URL=my-database.example.com
ENGINE_DATABASE=MY-DATABASE
ENGINE_USERNAME=user-name
ENGINE_PASSWORD=user-password

O usuário informado deverá ter uma política de segurança que permita o acesso remoto.

Automatização

É recomendado que seja adotada uma ferramenta de integração contínua que seja responsável por monitorar um branch de um repositório e caso haja uma alteração, dispare os comandos responsáveis por gerar o arquivo JAZ, execute os testes unitários e de integração, e atualize a Virtual File System com os arquivos JAZ construídos.