Executando verificação de segurança...
3

Por que ORMs não eliminam o problema entre código orientado a objetos e banco relacional?

Bancos de dados relacionais e aplicações orientadas a objetos trabalham com modelos mentais diferentes.

Essa diferença é conhecida como Object-Relational Impedance Mismatch.

O problema não é exatamente o banco relacional. Também não é exatamente a orientação a objetos. O problema aparece no atrito entre esses dois mundos.

No código, pensamos em objetos conectados entre si. No banco relacional, os dados são organizados em tabelas, linhas, colunas e relacionamentos.

Essa diferença parece pequena no começo, mas em sistemas maiores ela pode gerar bastante complexidade.


Em código orientado a objetos

Em uma aplicação orientada a objetos, é comum modelarmos entidades como classes ou objetos.

Exemplo em TypeScript:

class User {
  id: number;
  name: string;
  orders: Order[];
}

class Order {
  id: number;
  total: number;
  items: OrderItem[];
}

Nesse modelo, parece natural acessar os dados assim:

user.orders[0].items

Ou até algo mais profundo, como:

user.orders[0].items[0].product.name

No código, nós pensamos em um grafo de objetos.

Um usuário possui pedidos. Um pedido possui itens. Um item possui um produto.

A relação parece estar “dentro” do próprio objeto.


No banco relacional

No banco relacional, a estrutura é diferente.

Os dados normalmente ficam separados em tabelas.

Exemplo:

users
- id
- name

orders
- id
- user_id
- total

order_items
- id
- order_id
- product_id
- quantity

products
- id
- name
- price

O relacionamento entre usuário e pedidos não fica literalmente “dentro” da tabela de usuários.

Ele é representado por chaves primárias e chaves estrangeiras.

Por exemplo:

SELECT *
FROM users
JOIN orders ON orders.user_id = users.id
WHERE users.id = 1;

Ou seja, existe uma diferença entre:

  • o modelo de objetos da aplicação;
  • o modelo relacional do banco de dados.

Essa diferença é o centro do Object-Relational Impedance Mismatch.


Onde está o problema?

O problema aparece porque o código geralmente tenta trabalhar com objetos completos, enquanto o banco trabalha com dados normalizados e relacionados por chaves.

No código, você pensa:

user.orders

No banco, você precisa buscar dados em tabelas diferentes, fazer JOINs, aplicar filtros e reconstruir o objeto na aplicação.

Isso gera uma camada de complexidade entre a forma como a aplicação deseja usar os dados e a forma como o banco os armazena.


Problemas causados pelo Impedance Mismatch

1. Mapeamento entre objetos e tabelas

Classes nem sempre se encaixam naturalmente em tabelas.

Um objeto pode ter:

  • listas;
  • objetos aninhados;
  • herança;
  • métodos;
  • regras de negócio;
  • composição entre entidades.

Já o banco relacional trabalha com:

  • tabelas;
  • colunas;
  • linhas;
  • constraints;
  • relacionamentos;
  • operações baseadas em conjuntos.

Por isso, muitas vezes um objeto precisa ser quebrado em várias tabelas.

Um exemplo simples é um pedido.

No código, você pode ter um objeto Order com seus itens dentro dele.

No banco, provavelmente você terá pelo menos duas tabelas:

orders
order_items

Em sistemas maiores, esse número cresce rapidamente.


2. Reconstrução de objetos com JOINs

Para reconstruir um objeto completo, muitas vezes é necessário buscar dados em várias tabelas.

Por exemplo, para carregar um pedido completo, talvez seja necessário consultar:

  • pedido;
  • usuário;
  • endereço;
  • itens;
  • produtos;
  • pagamento;
  • entrega.

Isso pode gerar consultas mais complexas.

Mas é importante deixar claro: JOIN não é ruim por natureza.

Na verdade, JOIN é uma das grandes forças dos bancos relacionais.

O problema aparece quando a aplicação tenta reconstruir árvores de objetos muito profundas sem planejamento, carregando dados demais ou executando consultas mal otimizadas.


3. N+1 Query Problem

Esse é um problema clássico em aplicações que usam ORM.

Imagine que você busque 100 usuários.

Depois, para cada usuário, o sistema busca os pedidos separadamente.

O resultado pode ser algo assim:

1 query para buscar usuários
100 queries para buscar pedidos

Total:

101 queries

Isso é o famoso N+1 Query Problem.

O problema é que a aplicação parece simples no código, mas por baixo pode estar executando uma quantidade enorme de consultas.

Exemplo conceitual:

const users = await userRepository.findMany();

for (const user of users) {
  const orders = await orderRepository.findByUserId(user.id);
}

Dependendo do volume de dados, isso pode causar uma degradação séria de performance.


4. Lazy loading e eager loading

Dois conceitos importantes nesse contexto são lazy loading e eager loading.

Lazy loading

No lazy loading, os dados relacionados são carregados apenas quando forem acessados.

Isso pode parecer eficiente, mas também pode causar N+1 se não houver cuidado.

Exemplo conceitual:

const users = await getUsers();

for (const user of users) {
  console.log(user.orders);
}

Se orders for carregado sob demanda para cada usuário, você pode acabar disparando uma query para cada item da lista.

Eager loading

No eager loading, os dados relacionados são carregados junto com a consulta principal.

Exemplo com Prisma:

const users = await prisma.user.findMany({
  include: {
    orders: true
  }
});

Nesse caso, você já informa que quer buscar os usuários junto com seus pedidos.

Isso pode evitar N+1 em muitos cenários, mas também exige cuidado para não carregar dados desnecessários.


5. Uso de ORMs

ORMs como Prisma, TypeORM, Sequelize, Hibernate e Entity Framework tentam reduzir esse atrito mapeando objetos para tabelas.

Exemplo com Prisma:

const user = await prisma.user.findUnique({
  where: { id: 1 },
  include: {
    orders: true
  }
});

Isso facilita muito a vida do desenvolvedor.

Você não precisa escrever SQL manualmente para tudo, ganha produtividade e consegue trabalhar com uma API mais próxima da linguagem da aplicação.

Mas existe um ponto importante:

ORMs reduzem o atrito, mas não eliminam o impedance mismatch.

Eles criam uma camada de abstração entre objetos e tabelas, mas essa abstração pode vazar em consultas complexas, problemas de performance e decisões ruins de modelagem.

Problemas comuns com ORMs incluem:

  • queries ineficientes;
  • N+1 queries;
  • dificuldade para otimizar consultas;
  • abstração excessiva;
  • perda de controle sobre o SQL gerado;
  • migrations mal planejadas;
  • carregamento desnecessário de dados;
  • modelagem baseada demais no ORM e de menos no domínio.

ORM é ferramenta. Não é substituto para conhecimento de banco de dados.


Como lidar com Impedance Mismatch?

Não existe uma solução mágica, mas existem boas práticas que reduzem bastante esse problema.

1. Entender SQL de verdade

Mesmo usando ORM, é importante saber SQL.

Você precisa entender conceitos como:

  • JOIN;
  • índices;
  • transações;
  • constraints;
  • cardinalidade;
  • normalização;
  • planos de execução;
  • locks;
  • isolamento;
  • agregações;
  • paginação;
  • custo de consultas.

Quando você entende SQL, o ORM deixa de ser uma caixa-preta.

Você consegue olhar para uma query gerada e perceber se ela faz sentido ou não.


2. Modelar bem os dados

Antes de sair criando tabelas, é importante entender o domínio do sistema.

Algumas perguntas ajudam bastante:

  • quais entidades existem?
  • como elas se relacionam?
  • quais consultas serão mais comuns?
  • quais dados precisam ser consistentes?
  • quais operações precisam ser rápidas?
  • quais relacionamentos realmente precisam ser carregados juntos?
  • quais dados podem ser paginados?
  • quais dados podem ser consultados sob demanda?

Boa modelagem evita muita dor no futuro.

Um erro comum é modelar o banco pensando apenas no formato dos objetos da aplicação.

Outro erro comum é modelar a aplicação completamente presa ao formato das tabelas.

O equilíbrio está em entender o domínio, os casos de uso e as características do banco.


3. Usar ORM com cuidado

ORM pode ser excelente quando usado com consciência.

Algumas boas práticas:

  • analisar as queries geradas;
  • evitar carregamento desnecessário;
  • usar paginação;
  • criar índices adequados;
  • evitar N+1;
  • usar transações quando necessário;
  • usar select para buscar apenas os campos necessários;
  • usar include com cuidado;
  • usar raw SQL em consultas críticas;
  • medir performance antes de otimizar no escuro.

O problema não é usar ORM.

O problema é usar ORM sem entender o que está acontecendo no banco.


4. Separar modelo de domínio e modelo de persistência

Em sistemas maiores, pode fazer sentido separar:

  • o modelo usado na regra de negócio;
  • o modelo usado para persistir no banco.

Isso evita que o banco dite toda a estrutura da aplicação.

Também evita que a aplicação force o banco a seguir exatamente o formato dos objetos.

Essa separação é comum em arquiteturas mais maduras, especialmente quando o domínio começa a ficar mais complexo.

Por exemplo, o objeto usado na regra de negócio pode não ser idêntico ao modelo usado pelo ORM.

Você pode ter:

  • entidades de domínio;
  • DTOs;
  • models de persistência;
  • mappers;
  • repositories.

Isso aumenta um pouco a complexidade inicial, mas pode melhorar a manutenção em sistemas maiores.


Conclusão

O Object-Relational Impedance Mismatch não significa que bancos relacionais são ruins.

Também não significa que ORMs são vilões.

Ele apenas mostra que existe uma diferença real entre como pensamos os dados na aplicação e como eles são armazenados no banco.

No código, queremos trabalhar com objetos ricos, conectados e fáceis de navegar.

No banco relacional, os dados são organizados em tabelas, relações, constraints e operações baseadas em conjuntos.

ORMs ajudam a reduzir esse atrito, mas não eliminam a necessidade de entender banco de dados.

No fim, o desenvolvedor que entende essa diferença consegue:

  • usar ORMs com mais consciência;
  • escrever consultas melhores;
  • evitar problemas como N+1;
  • modelar sistemas mais consistentes;
  • tomar decisões melhores de performance;
  • saber quando usar abstração e quando escrever SQL direto.

Para mim, esse é um daqueles assuntos que parecem teóricos no começo, mas aparecem na prática assim que o sistema cresce.

E talvez a principal lição seja esta:

ORM facilita o acesso ao banco, mas não substitui o entendimento sobre como o banco funciona.

Carregando publicação patrocinada...