ORM - Interface com o Banco de Dados
Gabriel_Silvestre
Posted on April 7, 2022
Tabela de Conteúdos
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: não será abordado aqui, porém vale a pena dar uma olhada.
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
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.
- Instalação
- Inicialização
- Conexão com o DB
- Criação do Model, Seeds e Transactions
- Criação das Migrations
- Criação dos Operators
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.
npx sequelize model:generate --name <nome da tabela> --attributes <atributo: tipo>
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.
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*/;
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;
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:
npx sequelize migration:generate --name <nome da nova migration>
npx sequelize migration:generate --name add-column-phone-table-users
A estrutura gerada será assim:
Referência oficial
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.
// 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 */);
},
}
// 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.
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.
npx sequelize db:migrate
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.
npx sequelize seed:generate --name # nome do seeders
npx sequelize seed:generate --name users
A estrutura criada será:
'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.
'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
npx sequelize db:seed:all # irá executar todos os nossos seeders
npx sequelize db:seed:undo:all # irá reverter todos os nossos seeders
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:
// recupera todas as entidades de determinada tabela
Model.findAll();
// recupera uma entidade de determinada tabela de acordo com a Primary key
Model.findByPk();
// cria uma nova entidade em determinada tabela
Model.create();
// exclui uma ou mais entidades de acordo com as configurações definidas
Model.destroy();
Links Úteis
Models
Migrations
Seeders
Operations
Posted on April 7, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.