ORM - Interface com o Banco de Dados
Tags: javascript, webdev, beginners, braziliandevs
Published: 2022-04-07 15:03:17

## Tabela de Conteúdos
- [ORM](#orm)
- [Mapeamentos](#mapeamentos)
- [Sequelize](#sequelize)
- [Model](#model)
- [Migration](#migration)
- [Seeders](#seeders)
- [Operations](#operations)
- [Links Úteis](#links-úteis)

---

## ORM

### O que é?

Object Relational Mapper, ou em português, Mapeamento Objeto-Relacional, é uma técnica que permite realizar o mapeamento estrutural entre as entidades do DB, os representando através de objetos JS.

### Por que usar?

Como já vimos anteriormente, podemos realizar consultas e manipulações no servidor através de Queries SQL, porém em aplicações mais complexas isso se torna insustentável.

Imagine um cenário onde temos 10 tabelas, com 6 colunas cada, para inserir dados manualmente já seria uma tarefa "árdua", porém se pensarmos que cada tabela possui ao menos um relacionamento, essa tarefa já se torna extremamente complexa e sujeita a erros.

As ferramentas de ORM surgem justamente para simplificar e garantir maior segurança durante a integração entre a API e o Banco de Dados.

### ORMs

- Sequelize: será abordada mais à frente.
- [Prisma](https://www.prisma.io): não será abordado aqui, porém vale a pena dar uma olhada.

<br />

[Voltar ao topo](#tabela-de-conteúdos)

---

## Mapeamentos

### O que são?

São padrões que seguimos ao mapear uma entidade de um DB.

### Quais são?

Existem dois padrões de maior adoção, sendo eles o Data Mapper e o Active Record, ambos possuem suas próprias peculiaridades.

### Data Mapper

Esse padrão pede que a classe que representa a entidade não conheça os recursos necessários para realizar uma operação no DB, sendo assim temos que ter uma camada intermediária responsável pelas operações.

Com o padrão Data Mapper conseguimos ter a entidade mapeada (classe) completamente desacoplada do DB, sendo necessário refatorar apenas o Mapeador da entidade, que seria a camada intermediária.

### Active Record

Diferente do padrão mostrado acima, o Active Record centraliza todos os recursos na entidade mapeada, dessa forma nossa classe está diretamente acoplada ao DB, não sendo necessário criar uma camada intermediária.

A vantagem desse padrão é o desenvolvimento mais simples e rápido, comparado ao Data Mapper, porém por ter todos os recursos centralizados sua manutenção tende a ficar mais complexa conforme o projeto cresce

<br />

[Voltar ao topo](#tabela-de-conteúdos)

---

## Sequelize

### O que é?

É uma ferramenta ORM que segue o padrão de arquitetura Active Record para realizar o mapeamento das entidades.

### O que faz?

Como dito na definição de ORM, o Sequelize facilita a conexão entre API e DB, permitindo que realizemos operações dentro do DB sem utilizar Queries SQL, apenas usando JS.

### Implementando

Para implementarmos essa ferramenta precisamos seguir um passo a passo relativamente extenso.

1. Instalação
2. Inicialização
3. Conexão com o DB
4. Criação do Model, Seeds e Transactions
5. Criação das Migrations
6. Criação dos Operators

<br />

[Voltar ao topo](#tabela-de-conteúdos)

---

## Model

### O que é?

Tanto na arquitetura MSC, quanto no Sequelize, a Model é a camada na qual iremos realizar a conexão entre a API e o DB.

### Estrutura de Diretórios

Após iniciarmos nosso projeto através do Sequelize-Cli, um diretório chamado models será criado, dentro dele haverá um index.js, o qual não iremos mexer, ele é o responsável por estabelecer uma instância de conexão entre os arquivos de models/ e o DB.

### Criando models

Temos duas formas de criarmos Models, podemos utilizar do Sequelize-Cli, dessa forma não só a Model será gerada, mas a Migration também. Ou podemos fazer manualmente, criando um novo arquivo no diretório models/ criado anteriormente através do `npx sequelize init`.

#### Sequelize-Cli

Para criarmos novas Models utilizando o Sequelize-Cli, iremos utilizar o comando próprio para isso _(exemplificado logo abaixo)_ e passar o nome e os atributos que esse Model precisará.

Ao criamos uma nova Model através desse comando ela será criada como uma classe, então se ainda não estiver acostumado a Orientação a Objeto, criar a Model manualmente talvez seja uma opção melhor.

``` bash
npx sequelize model:generate --name <nome da tabela> --attributes <atributo: tipo>
```
``` bash
npx sequelize model:generate --name User --attributes fullName:string
```

_* Vale dizer que não precisamos passar todos os atributos através de comando, podemos adicioná-los depois diretamente no arquivo gerado_.

#### Manualmente

A Model pode ser criada manualmente, pois ela não possui nenhum tipo de identificador em seu nome de arquivo, logo a única coisa que precisamos fazer é criar um novo arquivo JS no diretório models/.

### Sintaxe

Após ter criado o arquivo dentro da pasta models/, gerada pelo Sequelize-Cli, com o arquivo criado nós iremos utilizar o `sequelize.define`, que é basicamente um método que nós permite definir Models através de uma função ao invés de uma classe.

Como parâmetro do método `.define()` iremos primeiro passar o nome da tabela e em seguida modelar a entidade, essa modelagem consiste em definir as colunas presentes em uma entidade, bem como seu tipo e restrições.

``` js
const /*nome da Model*/ = (sequelize, DataTypes) => {
  return sequelize.define(/*nome da Model*/, {
    /* nome do campo */: /* tipo do campo */,
    /* nome do campo */: /* tipo do campo */,
    /* nome do campo */: /* tipo do campo */,
  });
};

module.exports = /*Model*/;
```

``` js
const Person = (sequelize, DataTypes) => {
  return sequelize.define('Person', {
    id: DataTypes.INTEGER,  // <- O id é opcional, pois o Sequelize o irá definir por padrão
    firstName: DataTypes.STRING,
    lastName: DataTypes.STRING,
    age: DataTypes.INTEGER,
    /* ---------------------------- */
    // Assim como o id, os campos createdAt e updatedAt são opcionais
    // e serão criados pelo Sequelize por padrão.
    createdAt: DataTypes.DATE,
    updatedAt: DataTypes.DATE,
    /* ---------------------------- */
  });
};

module.exports = Person;
```

<br />

[Voltar ao topo](#tabela-de-conteúdos)

---

## Migration

### O que é?

É a forma de _"versionar"_ nosso DB, onde cada arquivo irá conter um pedaço de código que irá representar o histórico de alterações. Cada Migration deve saber exatamente quais alterações executar, para criar uma versão e restaurar versões anteriores.

### Estrutura de Diretórios

Todas as nossas Migrations irão ficar dentro do diretório migrations/, sendo que se utilizamos o comando do Sequelize-Cli para criarmos a Model a Migration terá sido criada junto.

### Criando migrations

As Migrations podem ser criadas de duas formas, podem ser criadas junto das Models, caso tenhamos o Sequelize-Cli para a criação das Models ou através do Sequelize-Cli.

#### Junto das Models

Ao criamos uma Model através do Sequelize-Cli uma Migration será gerada automaticamente, porém ela só terá os atributos definidos no comando executado, ou seja, se modificamos algo na Model após sua criação, será necessário fazer essa mesma modificação na Migration.

#### Sequelize-Cli

Para criarmos novas Migrations para uma tabela já existente, ou até mesmo gerar a primeira migration podemos usar um comando do Sequelize-Cli próprio para isso:

``` bash
npx sequelize migration:generate --name <nome da nova migration>
```
``` bash
npx sequelize migration:generate --name add-column-phone-table-users
```

A estrutura gerada será assim: <br />
_[Referência oficial](https://sequelize.org/docs/v6/other-topics/migrations/#migration-skeleton)_

``` js
module.exports = {
  up: (queryInterface, Sequelize) => {
    // logic for transforming into the new state
  },
  down: (queryInterface, Sequelize) => {
    // logic for reverting the changes
  }
}
```

### Sintaxe

A sintaxe da Migration é bem volátil, até porque irá mudar de acordo com o que desejarmos fazer no DB, a única constante é a utilização dos métodos da `queryInterface`.

Esses métodos são os responsáveis por interagir diretamente com o DB, podendo gerar/dropar tabelas, adicionar/excluir colunas, bem como executar Queries SQL puras.

``` js
// o exemplo genérico abaixo foca na criação e deleção de uma tabela.
module.exports = {
  async up(queryInterface, Sequelize) {
    await queryInterface.createTable(/* nome da tabela */, {
      /* nome do campo */: {
        /* opção do campo */: /* configuração */,
        /* opção do campo */: /* configuração */,
        /* opção do campo */: /* configuração */,
      },
      /* nome do campo */: {
        /* opção do campo */: /* configuração */,
        /* opção do campo */: /* configuração */,
      },
      /* nome do campo */: {
        /* opção do campo */: /* configuração */,
        /* opção do campo */: /* configuração */,
      },
      /* nome do campo */: /* tipo do campo */,
    });
  },
  async down(queryInterface, Sequelize) {
    await queryInterface.dropTable(/* nome da tabela */);
  },
}
```

``` js
// os métodos da queryInterface são assíncronos, então uma opção é utilizar async/await
module.exports = {
  async up(queryInterface, Sequelize) {
    await queryInterface.createTable('Persons', {  // criação da tabela Persons
      id: {
        type: Sequelize.INTEGER,
        primaryKey: true,
        autoIncrement: true,
      },
      firstName: {
        type: Sequelize.STRING,
        allowNull: false,
      },
      lastName: {
        type: Sequelize.STRING,
        allowNull: false,
      },
      age: {
        Sequelize.INTEGER,
        allowNull: false,
      },
      createdAt: {
        type: Sequelize.DATE,
        allowNull: false,
      },
      updatedAt: {
        type: Sequelize.DATE,
        allowNull: false,
      },
    });
  },
  async down(queryInterface, Sequelize) {
    await queryInterface.dropTable('Persons');  // deleção da tabela Persons
  },
}
```

Para saber quais métodos podem ser usados na `queryInterface` consulte a [documentação](https://sequelize.org/api/v6/class/src/dialects/abstract/query-interface.js~queryinterface).

### Executando a Migration

Após criar a Migration e realizar as configurações necessárias, precisamos executá-la para que as tabelas sejam criadas em nosso DB.

``` bash
npx sequelize db:migrate
```

<br />

[Voltar ao topo](#tabela-de-conteúdos)

---

## Seeders

### O que é?

É uma forma de povoar o DB para o funcionamento mínimo da aplicação.

### Estrutura de Diretório

Nossos arquivos de povoamento irão ficar no diretório seeders/ e assim como as Migrations, os Seeders deverão conter código para realizar e desfazer alterações.

### Criando seeders

Os Seeders devem ser criados a partir do Sequelize-Cli, isso porque os arquivos contém um `timestamp` em seu nome, então para evitar erros, o mais seguro é criá-los via linha de comando.

#### Sequelize-Cli

Para criar um Seeder via CLI tudo que precisamos fazer é executar o respectivo comando do Sequelize-Cli.

``` bash
npx sequelize seed:generate --name # nome do seeders
```
``` bash
npx sequelize seed:generate --name users
```

A estrutura criada será:

``` js
'use strict';

module.exports = {
  async up (queryInterface, Sequelize) {
    /**
     * Add seed commands here.
     *
     * Example:
     * await queryInterface.bulkInsert('People', [{
     *   name: 'John Doe',
     *   isBetaMember: false
     * }], {});
    */
  },

  async down (queryInterface, Sequelize) {
    /**
     * Add commands to revert seed here.
     *
     * Example:
     * await queryInterface.bulkDelete('People', null, {});
     */
  }
};
```

### Sintaxe

Para definirmos os dados a serem criados utilizamos a `queryInterface` juntamente do método `bulkInsert`, esse que irá receber o nome da tabela como primeiro parâmetro e os dados a serem inseridos como um Array no segundo parâmetro.

Já para deletar os dados utilizamos o método `bulkDelete`, esse que irá receber o nome da tabela e as opções de deleção, respectivamente, como parâmetros. Se quisermos deletar todos os dados, passamos `null` como segundo parâmetro.

``` js
'use strict';

module.exports = {
  up: async (queryInterface, Sequelize) => queryInterface.bulkInsert('Persons',
    [
      {
        firstName: 'John'
        lastName: 'Doe',
        email: 'john-doe@test.com',
        createdAt: Sequelize.literal('CURRENT_TIMESTAMP'),
        updatedAt: Sequelize.literal('CURRENT_TIMESTAMP'),
        // para criar as datas no SQL utilizamos o literal('CURRENT_TIMESTAMP')
      },
      {
        firstName: 'Joseph'
        lastName: 'Clinton',
        email: 'josph-clinton@test.com',
        createdAt: Sequelize.literal('CURRENT_TIMESTAMP'),
        updatedAt: Sequelize.literal('CURRENT_TIMESTAMP'),
      },
    ]),

  down: async (queryInterface) => queryInterface.bulkDelete('Persons', null, {}),
};
```

### Executando

Com o Seeders criados podemos popular nosso DB, para isso podemos executar os seguintes comandos

``` bash
npx sequelize db:seed:all  # irá executar todos os nossos seeders
```
``` bash
npx sequelize db:seed:undo:all  # irá reverter todos os nossos seeders
```

<br />

[Voltar ao topo](#tabela-de-conteúdos)

---

## Operations

### O que são?

São as formas pelas quais iremos interagir com a nossa camada Model.

### Como fazer?

Diferente de quando realizamos uma conexão direta com o DB, quando utilizamos o Sequelize nós não precisamos utilizar Queries SQL, basta utilizarmos os métodos disponibilizados pela própria ferramenta.

_* Existem casos onde será necessário utilizar Queries SQL junto do Sequelize, mas são raros._

### Onde fazer?

Isso irá depender da arquitetura escolhida, por exemplo, caso optemos por seguir o padrão MSC, então nossas operações deve ser feitas na camada de Service.

### Lista de operações

As operações mais comuns são:

``` js
  // recupera todas as entidades de determinada tabela
Model.findAll();
```

``` js
// recupera uma entidade de determinada tabela de acordo com a Primary key
Model.findByPk();
```

``` js
// cria uma nova entidade em determinada tabela
Model.create();
```

``` js
// exclui uma ou mais entidades de acordo com as configurações definidas
Model.destroy();
```

<br />

[Voltar ao topo](#tabela-de-conteúdos)

---

## Links Úteis

### Models

- [Documentação oficial](https://sequelize.org)
- [Tipagem das models](https://sequelize.org/docs/v6/core-concepts/model-basics/#data-types)

### Migrations

- [Query Interface](https://sequelize.org/api/v6/class/src/dialects/abstract/query-interface.js~queryinterface)
- [Tipagem Migrations](https://sequelize.org/api/v6/variable/index.html#static-variable-DataTypes)
- [Create Table](https://sequelize.org/api/v6/class/src/dialects/abstract/query-interface.js~queryinterface#instance-method-createTable)
- [Drop Table](https://sequelize.org/api/v6/class/src/dialects/abstract/query-interface.js~queryinterface#instance-method-dropTable)
- [Add Column](https://sequelize.org/api/v6/class/src/dialects/abstract/query-interface.js~queryinterface#instance-method-addColumn)
- [Remove Column](https://sequelize.org/api/v6/class/src/dialects/abstract/query-interface.js~queryinterface#instance-method-removeColumn)

### Seeders

- [Query Interface](https://sequelize.org/api/v6/class/src/dialects/abstract/query-interface.js~queryinterface)
- [Bulk Insert](https://sequelize.org/api/v6/class/src/dialects/abstract/query-interface.js~queryinterface#instance-method-bulkInsert)
- [Bulk Update](https://sequelize.org/api/v6/class/src/dialects/abstract/query-interface.js~queryinterface#instance-method-bulkUpdate)
- [Bulk Delete](https://sequelize.org/api/v6/class/src/dialects/abstract/query-interface.js~queryinterface#instance-method-bulkDelete)

### Operations
- [Todos os métodos dos Models](https://sequelize.org/api/v6/class/src/model.js~model)
- [Create](https://sequelize.org/api/v6/class/src/model.js~model#static-method-create)
- [Update](https://sequelize.org/api/v6/class/src/model.js~model#static-method-update)
- [Delete](https://sequelize.org/api/v6/class/src/model.js~model#static-method-destroy)
- [Find All](https://sequelize.org/api/v6/class/src/model.js~model#static-method-findAll)
- [Find by Pk](https://sequelize.org/api/v6/class/src/model.js~model#static-method-findByPk)
- [Find One](https://sequelize.org/api/v6/class/src/model.js~model#static-method-findOne)

<br />

[Voltar ao topo](#tabela-de-conteúdos)