DEV Community

Gabriel_Silvestre
Gabriel_Silvestre

Posted on

ORM - Interface com o Banco de Dados

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.

Voltar ao topo


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

Voltar ao topo


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

Voltar ao topo


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>
Enter fullscreen mode Exit fullscreen mode
npx sequelize model:generate --name User --attributes fullName:string
Enter fullscreen mode Exit fullscreen mode

* 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*/;
Enter fullscreen mode Exit fullscreen mode
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;
Enter fullscreen mode Exit fullscreen mode

Voltar ao topo


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>
Enter fullscreen mode Exit fullscreen mode
npx sequelize migration:generate --name add-column-phone-table-users
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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 */);
  },
}
Enter fullscreen mode Exit fullscreen mode
// 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
  },
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Voltar ao topo


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
Enter fullscreen mode Exit fullscreen mode
npx sequelize seed:generate --name users
Enter fullscreen mode Exit fullscreen mode

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, {});
     */
  }
};
Enter fullscreen mode Exit fullscreen mode

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, {}),
};
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
npx sequelize db:seed:undo:all  # irá reverter todos os nossos seeders
Enter fullscreen mode Exit fullscreen mode

Voltar ao topo


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();
Enter fullscreen mode Exit fullscreen mode
// recupera uma entidade de determinada tabela de acordo com a Primary key
Model.findByPk();
Enter fullscreen mode Exit fullscreen mode
// cria uma nova entidade em determinada tabela
Model.create();
Enter fullscreen mode Exit fullscreen mode
// exclui uma ou mais entidades de acordo com as configurações definidas
Model.destroy();
Enter fullscreen mode Exit fullscreen mode

Voltar ao topo


Links Úteis

Models

Migrations

Seeders

Operations

Voltar ao topo

Oldest comments (0)