Olá, bom dia, aqui será apresentado uma documentação relativa a estrutura de todas as aplicações que forem criadas dentro desse monorepo, o objetivo dessa documentação é expôr de maneira clara e objetiva o funcionamento da aplicação bem como sua estrutura. Os padrões aqui impostos seguem conceitos como Clean Code, Clean Architecture, SOLID, DRY, KISS e Design Patterns. Essa documentação também tem correlação ao TypeScript Style Guide proposto pelo Google. Uma outra fonte de inspiração foi o repositório Clean Code Typescript, que adapta conceitos conhecidos do Clean Code para o Typescript.
Para rodar esse projeto é necessário o mesmo estar dentro da pasta projects no repositório docker-development para ser executado, para mais informações, favor conferir o Readme do docker-development!
npm install @commitlint/config-conventional @commitlint/cli --save-dev
É possível gerenciar as configurações do commitlint no arquivo commitlint.config.js
npm install -g husky
npm install husky@^8.0.3 --save-dev
Adicione o script abaixo do husky no package.json
"scripts": {
"prepare": "husky install"
}
Para finalizar adicione os arquivos "commit-msg" e "husky.sh" da pasta .husky como hooks; OBS: Em alguns casos sera adicionado um "undefined" no final de ambos arquivos, é necessário remover esse "undefined" de ambos, senão o hook do husky não irá funcionar!
npx husky add .husky/husky.sh
npx husky add .husky/commit-msg
npx husky add .husky/pre-commit
npx husky add .husky/pre-push
npx husky add .husky/open-merge
npm install -g commitizen
npm install commitizen --save-dev
commitizen init cz-conventional-changelog
Adicione o script abaixo do commitzen no package.json
"scripts": {
"commit": "git-cz"
}
Agora usando o commando "git-cz" será aberta uma interface onde você pode escolher qual o tipo de categoria do commit (feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert); Selecionando a categoria você poderá digitar o escopo que pertence o commit, uma mensagem curta sobre o commit, e caso deseje adicionar mais detalhes também terá a opção de colocar uma descrição;
Caso tenha alguma dúvida ou queira personalizar mais a configuração, segue abaixo o artigo em que foi inspirado a implementação dessas ferramentas:
Padronização de commit com (Commitlint, Husky e Commitizen)
Rota do swagger de todas as aplicações do monorepo é sempre o virtual host definido no docker-compose dentro da pasta .docker do docker-development com o sufixo "/docs" no final!
Para baixar todas as libs do projeto, rodar o comando abaixo
git submodule update --init
Principais ferramentas utilizadas:
- Nestjs
- Husky
- CommitLint
- Commitzen
- Padronização de commit com (Commitlint, Husky e Commitizen)
- Typescript
- ESLint
- ESLint SonarJS
- Prettier
- Lint Staged
- Jest
- Jest Mock Extended
- Compodoc
- docker
O Projeto segue o seguinte modelo de orgranização de arquivos e pastas:
/...
/src
|__/common
|__/config
|__/enum
|__/module
|__/moduleName
|__/application
|__/controller
|__/dto
|_/domain
|_/contract
|_/entity
|_/service
|_/infra
|_/repository
|_/adapter
|_/schema
/...
-
/src - Aqui se localiza a raíz da aplicação em questão;
-
/common - Aqui é armazendo pastas contendo arquivos cuja finalidade é utilizá-los na aplicação como um todo independente do seu local;
- /config - Local os se localiza o mapeamento das variáveis de ambiente(env) da aplicação
- /enum - Essa pasta compreende os enums(identificadores) utilizados na aplicação como um todo
-
/module -> Aqui são armazenados os módulos da aplicação;
-
/module/moduleName/application:
- /controller - Essa pasta compreende os controllers da aplicação;
- /dto - Aqui se localiza as definições de entrada(IN) e saída(OUT) dos controllers;
-
/module/moduleName/domain:
- /service: Compreende principalmente os services da aplicação;
- /entity - Entidade de conversão de dados, atuando como um de/para para os dados fornecidos pelo Banco de Dados;
- /contract - Local onde fica definido os métodos(queries) enviadas ao banco de dados;
-
/module/moduleName/infra:
- /repository: Aqui se localiza as queries executadas no banco de dados;
- /adapter: Diretório que compreende conexões com provedores de terceiros ou outras aplicações em diferentes contextos;
- /schema: Local onde são armazenadas as Tabelas e/ou Schemas/Models relativas ao Banco de Dados;
CASO QUEIRA UMA VISUALIZAÇÃO MAIS DIDÁTICA VOCÊ PODE RODAR O COMPODOC COM O COMANDO:
npm run compodoc:build
npm run compodoc:run
ACESSANDO O http://localhost:4000 UMA INTERFACE VISUAL SERÁ APRESENTADA COM A ESTRUTURAÇÃO DE TODOS OS MÓDULOS DO PROJETO!
- Neste projeto utilizamos o Jest como framework para a execução de testes unitários e e2e. Todas as configurações utilizadas podem ser encontradas no arquivo
jest.config.json
presente na raiz do projeto. - Os arquivos contendo testes unitários devem estar no mesmo diretório do código de produção. Por padrão, os arquivos de teste devem ter o mesmo nome do arquivo onde está a implementação do que está sendo testado, com a adição do sufixo
spec
, por exemplo,my-code.service.spec.ts
. - Os testes e2e ficam no diretório
test
, na raiz do projeto, e são organizados por módulos. Esse tipo de teste é utilizado para testar o fluxo da aplicação como um todo, fazendo um request para um endpoint e recebendo uma response. Para efetuar requests nos testes e2e é utilizado a lib Supertest em conjunto com o Jest. O nome dos arquivos de teste deve ter o seguinte padrão: nome do módulo + sufixo e2e-spec, como por exemplo,my-module.e2e-spec.ts
. - Este projeto também está configurado para gerar relatórios de execução de testes e de coverage. Os de execução podem ser encotrados no diretório
reports
e os de coverage emcoverage
, ambos na raiz da aplicação. - Os seguintes scripts estão configurados para a execução dos testes:
test
- executa todos os testes da aplicação.test:watch
- executa os testes em watch mode.test:staged
- executa os testes relacionados aos arquivos da staging area do git.test:cov
- executa todos os testes da aplicação e gera o relatório de coverage.test:debug
- executa os testes no modo debug.test:e2e
- executa os testes e2e.
O Husky é uma ferramenta utilizada para a criação e execução de git hooks, permitindo executar lint, tests, etc... ao fazer um commit ou push. Neste projeto temos três hooks configurados:
- commit-msg: Hook executado quando um commit é realizado. Visa validar, juntamente com o commintlint, se todos os commits estão dentro do padrão estabelecido.
- pre-commit: Hook executado antes de todos os commits. Com o auxílio do lint-staged executa os testes e lint para todos os arquivos que foram alterados.
- pre-push: Hook executado antes do push para o repositório de origem. É utilizado para rodar os testes e lint de todo projeto antes de confirmar o push para a origem.
Para melhor entendimento do funcionamento dessa biblioteca, basta acessar o guia disponível na documentação oficial do Husky.
O ESLint/Prettier/SonarJS analisa estaticamente o código da aplicação para encontrar e corrigir problemas de lint rapidamente. Todas as configurações de plugins e regras de lint utilizadas estão no arquivo .eslintrc.js
na raiz da aplicação.
Para executar o lint basta rodar o seguinte comando: npm run lint
.
Caso queira entender melhor sobre o funcionamento das bibliotecas, recomendamos a consulta em suas documentações oficiais: ESLint, ESLint SonarJS, Prettier.
- Use PascalCase para nomes de classes, types e interfaces:
MyClass
. - Use camelCase para nomes de variáveis, funções e métodos:
exampleVariable
,myFunction()
. - Use kebab-case + nome da pasta para nome dos arquivos:
my-code.service.ts
,my-code.repository.ts
. - Para testes, siga o mesmo padrão de nomes de arquivos, com a adição do sufixo spec:
my-code.service.spec.ts
,my-code.repository.spec.ts
. - Use snake_case + uppercase para nomes de constantes e enums:
MAX_LIMIT
,MIN_LIMIT
.
1. Defina variáveis com nomes pronunciáveis e que façam sentido. Isso também serve para tipos, funções e etc.
Bad:
type DtaRcrd102 = {
genymdhms: Date;
modymdhms: Date;
pszqint: number;
};
Good:
type Customer = {
generationTimestamp: Date;
modificationTimestamp: Date;
recordId: number;
};
Bad:
// What the heck is 86400000 for?
setTimeout(restart, 86400000);
Good:
// Declare them as capitalized named constants.
const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000; // 86400000
setTimeout(restart, MILLISECONDS_PER_DAY);
Explicit is better than implicit. Clarity is king. Bad:
const u = getUser();
const s = getSubscription();
const t = charge(u, s);
Good:
const user = getUser();
const subscription = getSubscription();
const transaction = charge(user, subscription);
Bad:
function loadPages(count?: number) {
const loadCount = count !== undefined ? count : 10;
// ...
}
Good:
function loadPages(count: number = 10) {
// ...
}
Bad:
const GENRE = {
ROMANTIC: 'romantic',
DRAMA: 'drama',
COMEDY: 'comedy',
DOCUMENTARY: 'documentary',
};
configureFilm(GENRE.COMEDY);
Good:
enum GENRE {
ROMANTIC,
DRAMA,
COMEDY,
DOCUMENTARY,
}
configureFilm(GENRE.COMEDY);
Bad:
function createMenu(
title: string,
body: string,
buttonText: string,
cancellable: boolean,
) {
// ...
}
createMenu('Foo', 'Bar', 'Baz', true);
Good:
type MenuOptions = {
title: string;
body: string;
buttonText: string;
cancellable: boolean;
};
function createMenu(options: MenuOptions) {
// ...
}
createMenu({
title: 'Foo',
body: 'Bar',
buttonText: 'Baz',
cancellable: true,
});
Bad:
function emailActiveClients(clients: Client[]) {
clients.forEach((client) => {
const clientRecord = database.lookup(client);
if (clientRecord.isActive()) {
email(client);
}
});
}
Good:
function emailActiveClients(clients: Client[]) {
clients.filter(isActiveClient).forEach(email);
}
function isActiveClient(client: Client) {
const clientRecord = database.lookup(client);
return clientRecord.isActive();
}
Bad:
function addToDate(date: Date, month: number): Date {
// ...
}
const date = new Date();
// It's hard to tell from the function name what is added
addToDate(date, 1);
Good:
function addMonthToDate(date: Date, month: number): Date {
// ...
}
const date = new Date();
addMonthToDate(date, 1);