Object-Relational Mapping (ORM)

Mapeamento Objeto-Relacional

Agenda

  1. O que é ORM?
  2. Vantagens e Desvantagens
  3. Principais ORMs
  4. Prisma em Detalhes
  5. CRUD com Prisma
  6. Boas Práticas
  7. Comparativo entre ORMs

1. O que é ORM?

Conceito

ORM (Object-Relational Mapping) é uma técnica de programação que converte dados entre sistemas incompatíveis de tipos em linguagens orientadas a objetos.

┌─────────────────┐     ┌─────────────────┐
│   Objeto (OOP)  │ ←→  │   Tabela SQL    │
│   { id, nome }  │     │   id, nome      │
└─────────────────┘     └─────────────────┘

Problema que o ORM Resolve

Impedance Mismatch - O "descompasso" entre:

  • Modelo Relacional: Tabelas, linhas, chaves estrangeiras
  • Modelo Orientado a Objetos: Classes, objetos, herança, composição

Como Funciona

// Sem ORM - SQL Puro
const users = await db.query("SELECT * FROM users WHERE age > ?", [18]);

// Com ORM - Prisma
const users = await prisma.user.findMany({
  where: { age: { gt: 18 } },
});

2. Vantagens e Desvantagens

Vantagens ✓

Benefício Descrição
Produtividade Menos código, desenvolvimento mais rápido
Abstração Não precisa escrever SQL manualmente
Type Safety Autocompletes e validação de tipos
Portabilidade Troca de banco com poucas alterações
Relacionamentos Maneja automaticamente joins
Migrações Versionamento do schema

Desvantagens ✗

Problema Descrição
Curva de Aprendizado Sintaxe própria para aprender
Performance Pode ser mais lento que SQL puro
Complexidade Consultas simples podem ficar verbosas
Abstração Vazada Às vezes necessário usar SQL puro
Overhead Camada adicional na aplicação

3. Principais ORMs

ORMs para Node.js/TypeScript

ORM Linguagem Destaque
Prisma TypeScript Type-safe, gerador de cliente
TypeORM TypeScript Decorators, similar a Hibernate
Sequelize JavaScript Um dos mais antigos, maduro
MikroORM TypeScript Unit of Work, Identity Map
Objection.js JavaScript Baseado em Knex, SQL Builder
Drizzle TypeScript SQL-like, zero overhead

ORMs para Outras Linguagens

Linguagem ORM Principal Característica
Python SQLAlchemy Mais poderoso e flexível
Python Django ORM Batteries included
Python Peewee Simples e leve
Java Hibernate Padrão da indústria JPA
Java JPA/EclipseLink Especificação oficial
C# Entity Framework Integrado ao .NET
Ruby ActiveRecord Convenção sobre configuração
PHP Eloquent Laravel ORM
Go GORM Popular em Go

4. Prisma em Detalhes

O que é Prisma?

Prisma é um ORM de próxima geração para Node.js e TypeScript:

  • Type-safe: Autocompletes em tempo de desenvolvimento
  • Schema First: Define o schema em arquivo .prisma
  • Gerador de Cliente: Gera código TypeScript automaticamente
  • Migrações: Versionamento automático do banco

Arquitetura do Prisma

┌─────────────────────────────────────────────────┐
│              Sua Aplicação                       │
└─────────────────┬───────────────────────────────┘
                  │
┌─────────────────▼───────────────────────────────┐
│         Prisma Client (Gerado)                  │
│  - Métodos type-safe                            │
│  - Autocompletes                                │
└─────────────────┬───────────────────────────────┘
                  │
┌─────────────────▼───────────────────────────────┐
│            Prisma Engine (Rust)                 │
│  - Query Builder                                │
│  - Migrations                                   │
└─────────────────┬───────────────────────────────┘
                  │
┌─────────────────▼───────────────────────────────┐
│         Banco de Dados                          │
│  PostgreSQL, MySQL, SQLite, SQL Server, MongoDB│
└─────────────────────────────────────────────────┘

Prisma Schema

// schema.prisma

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  posts     Post[]
  createdAt DateTime @default(now())
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
}

Comandos Básicos do Prisma

# Inicializar Prisma
npx prisma init

# Criar migração
npx prisma migrate dev --name init

# Gerar cliente (sem migração)
npx prisma generate

# Abrir Prisma Studio (GUI)
npx prisma studio

# Resetar banco
npx prisma migrate reset

# Ver status das migrações
npx prisma migrate status

5. CRUD com Prisma

Configuração Inicial

// src/lib/prisma.ts
import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

export default prisma;

// Singleton para desenvolvimento (hot reload)
const globalForPrisma = global as unknown as { prisma: PrismaClient };

export const prisma = globalForPrisma.prisma || new PrismaClient();

if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;

CREATE - Criando Registros

// Criar um usuário simples
const user = await prisma.user.create({
  data: {
    email: "joao@example.com",
    name: "João Silva",
  },
});

// Criar usuário com posts aninhados
const userWithPosts = await prisma.user.create({
  data: {
    email: "maria@example.com",
    name: "Maria Santos",
    posts: {
      create: [
        {
          title: "Primeiro Post",
          content: "Conteúdo do post...",
          published: true,
        },
        {
          title: "Segundo Post",
          published: false,
        },
      ],
    },
  },
  include: {
    posts: true, // Incluir os posts criados na resposta
  },
});

READ - Lendo Registros

// Buscar todos os usuários
const allUsers = await prisma.user.findMany();

// Buscar um usuário por ID
const user = await prisma.user.findUnique({
  where: { id: 1 },
});

// Buscar com filtros
const adults = await prisma.user.findMany({
  where: {
    email: { contains: "@gmail.com" },
  },
});

// Buscar com filtros complexos
const posts = await prisma.post.findMany({
  where: {
    AND: [{ published: true }, { title: { contains: "Tutorial" } }],
    author: {
      email: { endsWith: "@empresa.com" },
    },
  },
});

READ - Relacionamentos e Ordenação

// Buscar usuário com seus posts
const userWithPosts = await prisma.user.findUnique({
  where: { id: 1 },
  include: {
    posts: {
      where: { published: true },
      orderBy: { createdAt: "desc" },
      take: 5, // Limitar resultados
    },
  },
});
// Selecionar campos específicos
const users = await prisma.user.findMany({
  select: {
    email: true,
    name: true,
    _count: {
      select: { posts: true },
    },
  },
});

// Paginação
const page = 1;
const pageSize = 10;
const users = await prisma.user.findMany({
  skip: (page - 1) * pageSize,
  take: pageSize,
});

UPDATE - Atualizando Registros

// Atualizar um usuário
const updatedUser = await prisma.user.update({
  where: { id: 1 },
  data: {
    name: "João Silva Jr.",
    email: "joao.jr@example.com",
  },
});
// Atualização condicional
const post = await prisma.post.updateMany({
  where: {
    authorId: 1,
    published: false,
  },
  data: {
    published: true,
  },
});

// Upsert (cria se não existe)
const user = await prisma.user.upsert({
  where: { email: "novo@example.com" },
  update: {},
  create: {
    email: "novo@example.com",
    name: "Novo Usuário",
  },
});

DELETE - Deletando Registros

// Deletar um usuário
const deletedUser = await prisma.user.delete({
  where: { id: 1 }
})

// Deletar condicionalmente
const result = await prisma.post.deleteMany({
  where: {
    published: false,
    createdAt: { lt: new Date('2024-01-01') }
  }
})
// result.count = número de registros deletados

// Deletar com relacionamento (cascade)
// Configurado no schema:
model User {
  posts Post[]  // onDelete: Cascade é padrão
}

CRUD Completo em uma API

import { Router } from "express";
import prisma from "../lib/prisma";

const router = Router();

// CREATE
router.post("/users", async (req, res) => {
  const { email, name } = req.body;
  const user = await prisma.user.create({
    data: { email, name },
  });
  res.json(user);
});

// READ (todos)
router.get("/users", async (req, res) => {
  const users = await prisma.user.findMany({
    include: { posts: true },
  });
  res.json(users);
});
// READ (um)
router.get("/users/:id", async (req, res) => {
  const user = await prisma.user.findUnique({
    where: { id: Number(req.params.id) },
    include: { posts: true },
  });
  res.json(user);
});

// UPDATE
router.put("/users/:id", async (req, res) => {
  const user = await prisma.user.update({
    where: { id: Number(req.params.id) },
    data: req.body,
  });
  res.json(user);
});

// DELETE
router.delete("/users/:id", async (req, res) => {
  await prisma.user.delete({
    where: { id: Number(req.params.id) },
  });
  res.status(204).send();
});

6. Boas Práticas

Boas Práticas com Prisma

  1. Use Transactions para operações atômicas
  2. Evite N+1 queries com include adequado
  3. Indexes em colunas frequentemente buscadas
  4. Validação antes de criar/atualizar
  5. Soft Delete em vez de delete físico
  6. Connection Pooling em produção

Transactions

await prisma.$transaction(async (tx) => {
  // Todas as operações falham se uma falhar
  const user = await tx.user.create({
    data: { email: "user@example.com", name: "User" },
  });

  await tx.post.create({
    data: {
      title: "Post do User",
      authorId: user.id,
    },
  });
});

Evitando N+1 Queries

// ❌ N+1 Problem
const users = await prisma.user.findMany();
for (const user of users) {
  const posts = await prisma.post.findMany({
    where: { authorId: user.id }, // 1 query por usuário!
  });
}

// ✓ Solução com include
const users = await prisma.user.findMany({
  include: { posts: true }, // Apenas 1 query!
});

Soft Delete

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  deletedAt DateTime?
}

// Schema deve ter:
// @@index([deletedAt])
// Buscar apenas não deletados
const activeUsers = await prisma.user.findMany({
  where: { deletedAt: null },
});

// Soft delete
await prisma.user.update({
  where: { id: 1 },
  data: { deletedAt: new Date() },
});

7. Comparativo entre ORMs

Matriz Comparativa

Feature Prisma TypeORM Sequelize Drizzle
Type Safety ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐ ⭐⭐⭐⭐⭐
Performance ⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐⭐
Learning Curve ⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐
Migrations ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐
Maturity ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐

Quando Usar Cada ORM

  • Prisma: Projetos novos com TypeScript, foco em DX
  • TypeORM: Projetos que precisam de decorators, complexo
  • Sequelize: Legado JavaScript, já estabelecido
  • Drizzle: Performance crítica, SQL-like
  • Raw SQL: Queries muito complexas, performance máxima

Conclusão

Recapitulando

  • ORM abstrai o acesso a bancos de dados
  • Prisma oferece type-safety e excelente DX
  • CRUD com Prisma é simples e intuitivo
  • Conheça os trade-offs de cada ORM
  • Use boas práticas: transactions, indexes, validação

Próximos Passos

  1. Instalar Prisma: npm install prisma @prisma/client
  2. Criar schema: npx prisma init
  3. Definir modelos no schema.prisma
  4. Criar migração: npx prisma migrate dev
  5. Começar a usar o cliente gerado!

Referências

Dúvidas?

Obrigado! 🚀