|
|
[Voltar para arquitetura](arquitetura)
|
|
|
|
|
|
# Guia de desenvolvimento - Backend
|
|
|
|
|
|
Este documento é leitura obrigatória antes de iniciar o desenvolvimento do backend. Ele contém informações importantes sobre arquitetura, documentação e organização física do projeto.
|
|
|
|
|
|
**Índice**
|
|
|
- [Organização física](#organização-física)
|
|
|
- [Documentação](#documentação)
|
|
|
- [Arquitetura](#arquitetura)
|
|
|
- [Controllers](#controllers)
|
|
|
- [Funcionamento básico](#funcionamento-básico)
|
|
|
- [Organização física](#organização-física-1)
|
|
|
- [Criando novas controllers](#criando-novas-controllers)
|
|
|
- [Implementando controllers](#implementando-controllers)
|
|
|
- [Services](#services)
|
|
|
- [Organização física](#organização-física-2)
|
|
|
- [Criando novos serviços](#criando-novos-serviços)
|
|
|
- [Implementando serviços](#implementando-serviços)
|
|
|
- [Models (DAOs)](#models-daos)
|
|
|
- [Organização física](#organização-física-3)
|
|
|
- [Criando novos modelos](#criando-novos-modelos)
|
|
|
- [Implementando modelos](#implementando-modelos)
|
|
|
|
|
|
## Organização física
|
|
|
|
|
|
As principais estruturas do projeto são:
|
|
|
|
|
|
- `README.md`: informações sobre o projeto, instruções de configuração do ambiente e documentação dos scripts de execução e manutenção
|
|
|
- `package.json` e `package-lock.json`: gerenciamento de dependências com NPM
|
|
|
- `.[babel,eslint,husky,jest,prettier]rc.js`, `.eslintignore` e `nodemon.json`: configurações de pacotes auxiliares (transpiler, linter, formatter, git hooks, hot reload, testes, ...)
|
|
|
- `.env`: variáveis de ambiente para execução do servidor
|
|
|
- `docs/`: documentação
|
|
|
- `update.js`: script para atualizar a documentação local
|
|
|
- `dTool API.postman_collection_.json`: collection do Postman com requests da API
|
|
|
- `DT.postman_environment.json`: environment do Postman com placeholders para as variáveis
|
|
|
- `DT-development.postman_environment.json`: environment do Postman com valores padrão para desenvolvimento
|
|
|
- `dist/`: código transpilado (que é executado pelo NodeJS)
|
|
|
- `src/`
|
|
|
- `loaders/`: funções de setup da aplicação
|
|
|
- `models/`: modelos/DAOs/persistência ([ver mais](#models-daos))
|
|
|
- `routes/`: controllers/rotas ([ver mais](#controllers))
|
|
|
- `services/`: serviços/negócio ([ver mais](#services))
|
|
|
- `app.js`: ponto de entrada do servidor ("main")
|
|
|
- `config.js`: exporta objeto com variáveis de ambiente (`.env`) para uso pela aplicação
|
|
|
|
|
|
## Documentação
|
|
|
|
|
|
Todas as rotas da aplicação serão documentadas usando o [Postman](https://www.postman.com/). O Postman é uma aplicação que permite a fácil execução de requests HTTP e análise dos retornos, ajudando a testar uma rota durante seu desenvolvimento (se o retorno da rota está de acordo com o especificado na documentação).
|
|
|
|
|
|
Você pode [consultar a documentação das rotas a serem desenvolvidas](https://documenter.getpostman.com/view/10919351/SzYYzJfz?version=latest) pelo navegador e importar a definição das rotas para o aplicativo usando o botão _Run in Postman_. Junto a cada rota, serão anexados exemplos de entrada e retorno esperado, servindo como referência para o desenvolvimento tanto do backend quanto do app.
|
|
|
|
|
|
## Arquitetura
|
|
|
|
|
|
O backend está organizado em uma arquitetura com três camadas:
|
|
|
- controllers (pasta `src/routes`): trata os parâmetros das rotas, utiliza serviços para executar as ações necessárias para o correto funcionamento da rota e controem o retorno da rota (código de status, formato, ...);
|
|
|
- services (pasta `src/services`): camada de negócio, recebe os parâmetros já parseados pelas controllers e usa os DAOs para acesso ao banco de dados;
|
|
|
- models (pasta `src/models`): camada de persistência, provê acesso ao banco de dados.
|
|
|
|
|
|
<p align="center">
|
|
|
<img src="Imagens/arq_backend_basico.png">
|
|
|
</p>
|
|
|
|
|
|
Seguindo o padrão arquitetural em camadas, é importante que objetos de uma camada troquem mensagens apenas com objetos da camada imediatamente inferior, ou objetos da mesma camada. Por exemplo: controllers podem acessar/usar apenas services; services podem usar outros services ou modelos/DAOs (mas não podem acessar controllers).
|
|
|
|
|
|
### Controllers
|
|
|
|
|
|
A camada de controllers (controladoras) é responsável por definir o tratamento para cada rota da API. Elas usam diretamente a API exposta pelo framework [Express](https://expressjs.com/), tratando os parâmetros de cada rota (sejam parâmetros recebidos no body do request, como parte do caminho da rota ou como query), convertendo-os para um formato/estrutura aceito pelo(s) serviço(s) usados, fazendo invocações aos serviços e construindo as respostas de acordo com o retorno dos serviços.
|
|
|
|
|
|
#### Funcionamento básico
|
|
|
|
|
|
1. tratar parâmetros usando API do Express;
|
|
|
2. ajustar parâmetros para formato/estrutura esperada pelo(s) serviço(s) usado(s);
|
|
|
3. executar a(s) chamada(s) ao(s) serviço(s);
|
|
|
4. construir a resposta do request (código de status e payload) a partir da resposta do(s) serviço(s).
|
|
|
|
|
|
#### Organização física
|
|
|
|
|
|
Todas as controllers estão na pasta `src/routes`, e devem ser organizados de acordo com a rota tratada. Por exemplo:
|
|
|
- o arquivo `src/routes/technology.js` trata os requests de todos os verbos aplicáveis (`GET`, `POST`, ...) à rota (e subrotas) `/api/technology`: `GET /api/technology/3`, `POST /api/tecnology`, ...
|
|
|
- da mesma forma, o arquivo `src/routes/healthInstitution.js` cuida da rota `/api/healthInstitution`.
|
|
|
|
|
|
O arquivo `src/routes/index.js` exporta uma função que faz a configuração de todas as rotas da API. Essa função é invocada por um loader como parte do processo de inicialização do servidor.
|
|
|
|
|
|
#### Criando novas controllers
|
|
|
|
|
|
Sempre que for preciso criar o controller para uma nova rota:
|
|
|
- o arquivo do controller deve exportar uma função que recebe um [Router](https://expressjs.com/en/4x/api.html#express.router)
|
|
|
- a função exportada pode ser `async` (`export default async (appRouter) => { ... }`)
|
|
|
- a controller deve ser adicionada ao processo de configuração no arquivo `src/routes/index.js`:
|
|
|
1. importar a controller: `import controller from "./controller"`
|
|
|
2. invocar a função da nova controller: `controller(appRouter)`
|
|
|
|
|
|
#### Implementando controllers
|
|
|
|
|
|
Os serviços que podem ser usados pelas controllers estão na pasta `src/services`, e devem ser importádos de lá.
|
|
|
|
|
|
> **Importante**
|
|
|
>
|
|
|
> Para manter a padronização, um serviço **não** deve ser importado a partir do arquivo onde está definido, mas apenas do arquivo de onde todos os serviços são exportados:
|
|
|
>
|
|
|
> - ❌ `import AlphaService from "../services/AlphaService"`
|
|
|
> - ✅ `import { AlphaService } from "../services"`
|
|
|
|
|
|
O acesso a serviços é feito com o uso de um [_service locator_](https://en.wikipedia.org/wiki/Service_locator_pattern): um objeto central que instancia e provê os serviços para uso pelas controllers. Para usar um serviço, é preciso:
|
|
|
|
|
|
1. importar o service locator: `import { Container } from "typedi"` (estamos usando o pacote [typedi](https://www.npmjs.com/package/typedi))
|
|
|
2. importar a classe do serviço: `import { AlphaService } from "../services"`
|
|
|
3. obter uma referência ao serviço: `const alphaService = Container.get(AlphaService)`
|
|
|
|
|
|
### Services
|
|
|
|
|
|
A camada de serviços é responsável pela lógica de negócio da aplicação. Ela faz as validações necessárias sobre os parâmetros, "joga" (`throw`) erros para tratamento pelas controllers, e faz modificações no banco de dados quando necessário. Um serviço também pode usar outro(s) serviço(s) para seu funcionamento.
|
|
|
|
|
|
Geralmente, um serviço é responsável pelas regras de negócio relacionadas a uma entidade de negócio. Por exemplo: `HealthInstitutionService` agrupa regras relativas às instituições de saúde; `TechnologyService`, relativos às tecnologias (procedimentos); e assim por diante.
|
|
|
|
|
|
> **Nota**
|
|
|
>
|
|
|
> Ainda não há uma definição exata quanto à granularidade e limite de responsabilidades dos serviços. Isso será discutido ao longo do projeto, caso a caso.
|
|
|
|
|
|
#### Organização física
|
|
|
|
|
|
Todos os serviços estão na pasta `src/services`. Serviços que podem ser usados por controllers devem ser exportados no arquivo `src/services/index.js`.
|
|
|
|
|
|
#### Criando novos serviços
|
|
|
|
|
|
Sempre que for necessário criar um novo serviço:
|
|
|
1. criar um arquivo com o nome da classe do serviço: `src/services/BetaService.js`
|
|
|
2. criar e exportar a classe do serviço: `export default class BetaService { ... }`
|
|
|
3. re-exportar o novo serviço no arquivo `src/services/index.js`: `export { default as BetaService } from "./BetaService"`
|
|
|
|
|
|
#### Implementando serviços
|
|
|
|
|
|
Não é possível definir uma regra geral de formato de implementação dos serviços: depende do objetivo do método e entidade relacionada. Entretanto, alguns métodos CRUD de serviços possuem uma implementação parecida:
|
|
|
|
|
|
1. validação das entradas
|
|
|
2. invocação de alguma operação da camada de modelos (_create_, _recover_, _update_, _delete_, ...)
|
|
|
3. filtro sobre o retorno da camada de serviço para retorno à controller
|
|
|
|
|
|
**Ressaltando:** não há um esqueleto de como é a implementação de um método de serviço.
|
|
|
|
|
|
### Models (DAOs)
|
|
|
|
|
|
A camada de modelos encapsula a persistência dos dados e comunicação com o banco de dados. Os objetos dessa camada também são chamados de DAO (_Data Access Object_), por conta do [padrão de projeto que implementam](https://en.wikipedia.org/wiki/Data_access_object).
|
|
|
|
|
|
Como o backend comunica-se com o SGBD PostgreSQL, essa camada é desenvolvida com o pacote [Sequelize](https://sequelize.org/), que faz o processo de mapeamento entre os modelos relacional e orientado a objetos (processo conhecido como [ORM](https://en.wikipedia.org/wiki/Object-relational_mapping)).
|
|
|
|
|
|
O uso do Sequelize como ORM permite acelerar o desenvolvimento ao não ter que implementar métodos CRUD simples que o Sequelize já provê, com _create_, _list_, _update_, entre outros.
|
|
|
|
|
|
Essa camada está intimamente ligada ao modelo físico de armazenamento dos dados no PostgreSQL: a definição de um modelo depende das tabelas, seus campos, chave primária e chaves estrangeiras (relacionamentos).
|
|
|
|
|
|
#### Organização física
|
|
|
|
|
|
Todos os modelos devem ficar na pasta `src/models`. O nome do arquivo de definição de cada modelo deve ser composto pelo nome do modelo (em _UpperCammelCase_), seguido de `DAO`. Cada arquivo deve definir apenas um modelo.
|
|
|
|
|
|
#### Criando novos modelos
|
|
|
|
|
|
Supondo que seja preciso criar um modelo para representar uma pessoa:
|
|
|
1. criar um arquivo `src/models/PersonDAO.js`
|
|
|
2. adicionar os imports necessários do Sequelize: `import { Model, DataTypes } from "sequelize"`
|
|
|
3. exportar uma classe _default_ que herda da classe `Model` do Sequelize: `export default class PersonDAO extends Model { ... }` (geralmente não há métodos customizados na classe; mais na próxima seção)
|
|
|
4. exportar uma função, de nome `setup`, que recebe como parâmetro o objeto do Sequelize para definição do modelo: `export const setup = (sequelize) => { ... }`
|
|
|
5. definir o modelo dentro da função `setup` usando a API do Sequelize.
|
|
|
|
|
|
#### Implementando modelos
|
|
|
|
|
|
Geralmente, não é preciso implementar operações customizadas na maioria dos modelos, apenas as operações providas pelo Sequelize são suficientes (_create_, _list_, _delete_, ...).
|
|
|
|
|
|
Entretanto, se necessário, é possível criar métodos customizados na classe do modelo:
|
|
|
|
|
|
```js
|
|
|
export default class PersonDAO extends Model {
|
|
|
static performComplexQuery(parameters) {
|
|
|
...
|
|
|
}
|
|
|
}
|
|
|
``` |