Database
 sql >> Base de Dados >  >> RDS >> Database

Comparando SQL, construtores de consultas e ORMs


Introdução


Usar um banco de dados para gerenciar os dados do aplicativo é uma das opções mais comuns para persistência de dados. Os bancos de dados permitem armazenamento e recuperação rápidos de informações, fornecem garantias de integridade de dados e oferecem persistência além da vida útil de uma instância de aplicativo individual. Existem inúmeros tipos de bancos de dados disponíveis para atender aos requisitos do seu projeto e às suas preferências.

No entanto, trabalhar diretamente com bancos de dados do seu aplicativo nem sempre é fácil. As diferenças na forma como as estruturas de dados são representadas geralmente levam a desafios. A dificuldade em expressar sutilezas sobre relacionamentos entre diferentes entidades também pode causar problemas. Para resolver isso, muitas ferramentas diferentes foram criadas para ajudar a atuar como uma interface entre o aplicativo principal e a camada de dados.

Neste guia, veremos algumas das diferenças que surgem entre três abordagens comuns:SQL bruto, construtores de consultas e ORMs (mapeadores relacionais de objeto). Vamos comparar algumas das vantagens e desvantagens de cada abordagem e, em seguida, terminar com um glossário de termos comumente usados ​​para ajudar a familiarizar-se com alguns conceitos-chave.

Como um resumo simplificado, aqui está uma visão geral dos pontos fortes e fracos de cada abordagem:
Abordagem Foco em banco de dados/programação Gerenciamento prático Nível de abstração Nível de complexidade
SQL bruto orientado a banco de dados alto nenhum baixo
Criadores de consultas misto baixo baixo baixo
ORMs orientado à programação baixo alto alto


Gerenciando dados com SQL bruto ou outra linguagem de consulta nativa de banco de dados


Alguns aplicativos interagem diretamente com o banco de dados escrevendo e executando consultas usando o idioma nativo suportado pelo mecanismo de banco de dados. Muitas vezes, um driver de banco de dados é tudo o que é necessário para conectar, autenticar e comunicar-se com a instância do banco de dados.

Os desenvolvedores podem enviar consultas escritas no idioma nativo do banco de dados por meio da conexão. Em troca, o banco de dados fornecerá os resultados da consulta, também em um de seus formatos nativos. Para muitos bancos de dados relacionais, a linguagem de consulta escolhida é SQL.

A maioria dos bancos de dados relacionais, bem como alguns bancos de dados não relacionais, suportam a linguagem de consulta estruturada, também conhecida como SQL, para criar e executar consultas poderosas. O SQL tem sido usado para gerenciar dados desde a década de 1970, por isso é bem suportado e padronizado até certo ponto.

Benefícios da consulta nativa


Usar SQL ou outra linguagem nativa de banco de dados tem alguns benefícios claros.

Uma vantagem é que os desenvolvedores escrevem e gerenciam as consultas ao banco de dados e tratam os resultados explicitamente. Embora isso possa dar muito trabalho adicional, significa que há poucas surpresas em termos do que o banco de dados está armazenando, como ele está representando seus dados e como ele fornecerá esses dados quando forem recuperados posteriormente. A falta de abstração significa que há menos "partes móveis" que podem levar à incerteza.

Um exemplo disso é o desempenho. Enquanto camadas de abstração sofisticadas geram consultas SQL traduzindo instruções de programação, o SQL gerado pode ser muito ineficiente. Cláusulas desnecessárias, consultas excessivamente amplas e outros contratempos podem levar a operações de banco de dados lentas que podem ser frágeis e difíceis de depurar. Ao escrever nativamente em SQL, você pode empregar todo o seu conhecimento de domínio e bom senso para evitar muitas classes de problemas de consulta

Outro motivo para usar a consulta nativa do banco de dados é a flexibilidade. Nenhuma abstração provavelmente será tão flexível quanto a linguagem de consulta de banco de dados nativa. Níveis mais altos de abstração tentam preencher a lacuna entre dois paradigmas diferentes, o que pode restringir os tipos de operações que eles podem expressar. Ao escrever em SQL bruto, no entanto, você pode aproveitar todos os recursos de seu mecanismo de banco de dados e expressar consultas mais complexas.


Desvantagens da consulta nativa


Embora a consulta nativa tenha alguns pontos fortes definidos, ela não está isenta de problemas.

Ao interagir com um banco de dados de um aplicativo usando SQL simples, você deve entender a estrutura de dados subjacente para compor consultas válidas. Você é totalmente responsável pela tradução entre os tipos de dados e estruturas que seu aplicativo emprega e as construções disponíveis no sistema de banco de dados.

Outra coisa a ter em mente ao trabalhar com SQL bruto é que cabe inteiramente a você gerenciar a segurança de sua entrada. Isso é especialmente verdadeiro se você estiver armazenando dados fornecidos por usuários externos, onde uma entrada especialmente criada pode induzir seu banco de dados a expor informações que você não pretendia permitir.

Esse tipo de exploração é chamado de injeção de SQL e é um problema potencial sempre que a entrada do usuário pode afetar o estado do banco de dados. As ferramentas de abstração mais altas geralmente limpam a entrada do usuário automaticamente, ajudando você a evitar essa classe de problemas.

Trabalhar com linguagens de consulta nativas quase sempre significa compor consultas com strings regulares. Isso pode ser um processo doloroso nos casos em que você deve escapar de entrada e concatenar strings para criar uma consulta válida. Suas operações de banco de dados podem ficar envolvidas em muitas camadas de manipulação de strings que têm um alto potencial de desfigurar dados acidentalmente.


Resumo da consulta nativa


Embora tenhamos falado principalmente sobre SQL nesta seção, a maioria das informações aqui se aplica igualmente bem a qualquer linguagem de consulta de banco de dados nativa. Para resumir, o SQL bruto ou o uso direto de qualquer linguagem de consulta equivalente aproxima você das abstrações usadas pelo banco de dados para armazenar e gerenciar os dados, mas força você a fazer todo o trabalho pesado de gerenciar seus dados manualmente.



Gerenciando dados com construtores de consultas


Uma abordagem alternativa ao uso de linguagens de consulta nativas de banco de dados, como SQL, é usar uma ferramenta ou biblioteca chamada construtor de consultas para conversar com seu banco de dados.

O que são construtores de consultas SQL?


Um construtor de consultas SQL adiciona uma camada de abstração acima das linguagens de consulta nativas do banco de dados bruto. Eles fazem isso formalizando padrões de consulta e fornecendo métodos ou funções que adicionam limpeza de entrada e itens de escape automaticamente para integração mais fácil em aplicativos.

As estruturas e ações suportadas pela camada de banco de dados ainda são bastante reconhecíveis ao usar construtores de consultas SQL. Isso permite que você trabalhe com dados programaticamente enquanto permanece relativamente próximo aos dados.

Normalmente, os construtores de consultas fornecem uma interface que usa métodos ou funções para adicionar uma condição a uma consulta. Ao encadear métodos, os desenvolvedores podem compor consultas de banco de dados completas a partir dessas "cláusulas" individuais.


Benefícios dos construtores de consultas SQL


Como os construtores de consultas usam as mesmas construções (métodos ou funções) que o restante de seu aplicativo, os desenvolvedores geralmente as consideram mais fáceis de gerenciar a longo prazo do que consultas de banco de dados brutas escritas como strings. É simples dizer a diferença entre operadores e dados e é fácil decompor as consultas em blocos lógicos que lidam com partes específicas de uma consulta.

Para alguns desenvolvedores, outra vantagem de usar um construtor de consultas SQL é que ele nem sempre oculta a linguagem de consulta subjacente. Embora as operações possam usar métodos em vez de strings, elas podem ser bastante transparentes, o que torna mais fácil para aqueles familiarizados com o banco de dados entender o que uma operação fará. Isso nem sempre é o caso ao usar níveis maiores de abstração.

Os construtores de consultas SQL geralmente também suportam vários back-ends de dados, abstraindo algumas das diferenças sutis em vários bancos de dados relacionais, por exemplo. Isso permite que você use as mesmas ferramentas para projetos que usam bancos de dados diferentes. Pode até tornar a migração para um novo banco de dados um pouco mais fácil.


Desvantagens dos construtores de consultas SQL


Os construtores de consultas SQL sofrem de algumas das mesmas desvantagens das linguagens de consulta nativas.

Uma crítica popular é que os construtores de consultas SQL ainda exigem que você entenda e considere as estruturas e os recursos do banco de dados. Esta não é uma abstração útil o suficiente para alguns desenvolvedores. Isso significa que você deve ter uma boa compreensão do SQL, além da sintaxe e dos recursos específicos do próprio construtor de consultas.

Além disso, os construtores de consultas SQL ainda exigem que você defina como os dados recuperados se relacionam com os dados do aplicativo. Não há sincronização automática entre os objetos na memória e os do banco de dados.

Embora os construtores de consultas muitas vezes emulem a linguagem de consulta com a qual foram projetados para trabalhar, a camada adicional de abstração pode significar que, às vezes, certas operações não são possíveis usando os métodos fornecidos. Normalmente, há um modo "bruto" para enviar consultas diretamente ao back-end, ignorando a interface típica do construtor de consultas, mas isso evita o problema em vez de resolvê-lo.


Resumo dos construtores de consultas SQL


No geral, os construtores de consultas SQL oferecem uma fina camada de abstração que visa especificamente alguns dos principais pontos problemáticos de trabalhar diretamente com linguagens nativas de banco de dados. Os construtores de consultas SQL quase funcionam como um sistema de modelos para consultas, permitindo que os desenvolvedores andem na linha entre trabalhar diretamente com o banco de dados e adicionar camadas adicionais de abstração.



Gerenciando dados com ORMs


Um passo adiante na hierarquia de abstração são os ORMs. Os ORMs geralmente visam uma abstração mais completa com a esperança de se integrar com os dados do aplicativo de forma mais fluida.

O que são ORMs?


Mapeadores objeto-relacionais, ou ORMs, são softwares dedicados à tradução entre as representações de dados em bancos de dados relacionais e a representação na memória usada com programação orientada a objetos (OOP). O ORM fornece uma interface orientada a objetos para dados dentro do banco de dados, tentando usar conceitos de programação familiares e reduzir a quantidade de código padrão necessário para acelerar o desenvolvimento.

Em geral, os ORMs servem como uma camada de abstração destinada a ajudar os desenvolvedores a trabalhar com bancos de dados sem alterar drasticamente o paradigma de orientação a objetos. Isso pode ser útil reduzindo a carga mental de adaptação às especificidades do formato de armazenamento de um banco de dados.

Em particular, objetos em programação orientada a objetos tendem a codificar muitos estados dentro deles e podem ter relacionamentos complexos com outros objetos por meio de herança e outros conceitos de POO. Mapear essas informações de maneira confiável em um paradigma relacional orientado a tabelas geralmente não é simples e pode exigir um bom entendimento de ambos os sistemas. Os ORMs tentam aliviar esse fardo automatizando parte desse mapeamento e fornecendo interfaces expressivas para os dados dentro do sistema.


Os desafios dos ORMs são específicos da programação orientada a objetos e bancos de dados relacionais?


Por definição, os ORMs são projetados especificamente para fazer interface entre linguagens de aplicativos orientadas a objetos e bancos de dados relacionais. No entanto, tentar mapear e traduzir entre as abstrações de estrutura de dados usadas em linguagens de programação e aquelas usadas por armazenamentos de banco de dados é um problema mais geral que pode existir sempre que as abstrações não se alinham corretamente.

Dependendo do paradigma de programação (orientado a objetos, funcional, procedural, etc.) e do tipo de banco de dados (relacional, documento, valor-chave, etc.), diferentes quantidades de abstração podem ser úteis. Muitas vezes, a complexidade das estruturas de dados dentro do aplicativo determina o quão fácil é a interface com o armazenamento de dados.

A programação orientada a objetos tende a produzir muitas estruturas com estados e relacionamentos significativos que devem ser considerados. Alguns outros paradigmas de programação são mais explícitos sobre onde o estado é armazenado e como ele é gerenciado. Por exemplo, linguagens puramente funcionais não permitem estado mutável, então estado é frequentemente uma entrada para funções ou objetos que geram um novo estado. Essa separação limpa de dados de ações, bem como a clareza dos ciclos de vida do estado, podem ajudar a simplificar a interação com o banco de dados.

De qualquer forma, a opção de interface com um banco de dados por meio de software que mapeia entre duas representações diferentes está frequentemente disponível. Portanto, embora os ORMs descrevam um subconjunto específico deles com desafios exclusivos, o mapeamento entre a memória do aplicativo e o armazenamento persistente geralmente requer consideração, independentemente dos detalhes.


Registro ativo vs ORMs do mapeador de dados


Diferentes ORMs empregam diferentes estratégias para mapear entre as estruturas do aplicativo e do banco de dados. As duas categorias principais são o padrão de registro ativo e o padrão de mapeador de dados .

O padrão de registro ativo tenta encapsular os dados do banco de dados dentro da estrutura de objetos em seu código. Os objetos contêm métodos para salvar, atualizar ou excluir do banco de dados e as alterações em seus objetos devem ser facilmente refletidas no banco de dados. Em geral, um objeto de registro ativo em seu aplicativo representa um registro em um banco de dados.

As implementações de registro ativo permitem que você gerencie seu banco de dados criando e conectando classes e instâncias em seu código. Como eles geralmente mapeiam instâncias de classe diretamente para registros de banco de dados, é fácil conceituar o que está em seu banco de dados se você entender quais objetos são usados ​​em seu código.

Infelizmente, isso também pode vir com algumas desvantagens importantes. Os aplicativos tendem a ser fortemente acoplados ao banco de dados, o que pode causar problemas ao tentar migrar para um novo banco de dados ou até mesmo ao testar seu código. Seu código tende a depender do banco de dados para preencher as lacunas que foram descarregadas de seus objetos. A tradução "mágica" entre esses dois domínios também pode levar a problemas de desempenho, pois o sistema tenta mapear objetos complexos de forma transparente para a estrutura de dados subjacente.

O padrão do mapeador de dados é o outro padrão ORM comum. Assim como o padrão de registro ativo, o mapeador de dados tenta atuar como uma camada independente entre seu código e seu banco de dados que faz a mediação entre os dois. No entanto, em vez de tentar integrar objetos e registros de banco de dados perfeitamente, ele se concentra em tentar desacoplar e traduzir entre eles, deixando cada um existir independentemente. Isso pode ajudar a separar sua lógica de negócios dos detalhes relacionados ao banco de dados que lidam com mapeamentos, representação, serialização etc.

Então, em vez de deixar o sistema ORM descobrir como mapear entre os objetos e as tabelas do banco de dados, o desenvolvedor é responsável por mapear explicitamente entre os dois. Isso pode ajudar a evitar o acoplamento apertado e as operações nos bastidores às custas de muito mais trabalho na descoberta de mapeamentos apropriados.


Benefícios dos ORMs


Os ORMs são populares por muitas razões.

Eles ajudam a abstrair o domínio de dados subjacente para algo que seja fácil de raciocinar dentro do contexto de seu aplicativo. Em vez de pensar no armazenamento de dados como um sistema independente, os ORMs ajudam você a acessar e gerenciar sistemas de dados como uma extensão de seu trabalho atual. Isso pode ajudar os desenvolvedores a trabalhar mais rápido na lógica de negócios principal, em vez de ficarem presos nas nuances de seus back-ends de armazenamento.

Outro efeito colateral disso é que os ORMs removem muito do clichê necessário para a interface com bancos de dados. Os ORMs geralmente vêm com ferramentas de migração que ajudam a gerenciar as alterações do esquema do banco de dados com base nas alterações feitas em seu código. Você não precisa necessariamente descobrir o esquema de banco de dados perfeito antecipadamente se o ORM puder ajudar a gerenciar as alterações na estrutura do banco de dados. As alterações no aplicativo e no banco de dados geralmente são a mesma coisa ou estão intimamente relacionadas, o que ajuda a rastrear as alterações no banco de dados à medida que você faz alterações no código.


Desvantagens dos ORMs


Os ORMs não são isentos de falhas. Em muitos casos, elas surgem das mesmas decisões que tornam os ORMs úteis.

Um dos problemas fundamentais com os ORMs é a tentativa de ocultar os detalhes do back-end do banco de dados. Essa ofuscação facilita o trabalho com ORMs em casos simples ou em pequenas escalas de tempo, mas geralmente leva a problemas à medida que a complexidade aumenta.

A abstração nunca está 100% completa e tentar usar um ORM sem entender a linguagem de consulta subjacente ou a estrutura do banco de dados geralmente leva a suposições problemáticas. Isso pode dificultar ou impossibilitar a depuração e o ajuste de desempenho.

Talvez o problema mais conhecido de trabalhar com ORMs seja a incompatibilidade de impedância objeto-relacional, um termo usado para descrever a dificuldade de traduzir entre a programação orientada a objetos e o paradigma relacional usado por bancos de dados relacionais. As incompatibilidades entre os modelos de dados usados ​​por essas duas categorias de tecnologia significam que uma abstração adicional e imperfeita é necessária a cada aumento de complexidade. A incompatibilidade de impedância objeto-relacional tem sido chamada de Vietnã da ciência da computação (em referência à Guerra do Vietnã) por causa de sua tendência de aumentar a complexidade ao longo do tempo e levar a situações em que os caminhos para o sucesso ou a mudança de curso são difíceis ou impossíveis.

Em geral, os ORMs tendem a ser mais lentos que as alternativas, especialmente com consultas complexas. Os ORMs geralmente geram consultas complicadas para operações de banco de dados relativamente simples, porque empregam padrões gerais que devem ser flexíveis o suficiente para lidar com outros casos. A confiança no ORM para fazer a coisa certa em todas as circunstâncias pode levar a erros dispendiosos que podem ser difíceis de detectar antecipadamente.


Resumo dos ORMs


Os ORMs podem ser abstrações úteis que facilitam muito o trabalho com bancos de dados. Eles podem ajudá-lo a projetar e iterar rapidamente e unir as diferenças conceituais entre a lógica do aplicativo e as estruturas do banco de dados. No entanto, muitas dessas vantagens funcionam como uma faca de dois gumes. Eles podem impedir que você entenda seus bancos de dados e podem dificultar a depuração, alterar paradigmas ou aumentar o desempenho.



Glossário


Ao trabalhar com tecnologias que fazem interface entre bancos de dados e aplicativos, você pode encontrar alguma terminologia com a qual não está familiarizado. Nesta seção, examinaremos brevemente alguns dos termos mais comuns que você pode encontrar, alguns dos quais foram abordados anteriormente neste artigo e outros não.
  • Mapeador de dados: Um mapeador de dados é um padrão de design ou software que mapeia estruturas de dados de programação para aquelas armazenadas em um banco de dados. Os mapeadores de dados tentam sincronizar as alterações entre as duas fontes, mantendo-as independentes uma da outra. O mapeador em si é responsável por manter uma tradução funcional, liberando os desenvolvedores para iterar as estruturas de dados do aplicativo sem se preocupar com a representação do banco de dados.
  • Driver de banco de dados: Um driver de banco de dados é um software projetado para encapsular e habilitar conexões entre um aplicativo e um banco de dados. Os drivers de banco de dados abstraem os detalhes de baixo nível de como fazer e gerenciar conexões e fornecem uma interface unificada e programática para o sistema de banco de dados. Normalmente, os drivers de banco de dados são o nível mais baixo de abstração que os desenvolvedores usam para interagir com bancos de dados, com ferramentas de nível superior que se baseiam nos recursos fornecidos pelo driver.
  • Ataque de injeção: Um ataque de injeção é um ataque no qual um usuário mal-intencionado tenta executar operações de banco de dados indesejadas usando entradas especialmente criadas em campos de aplicativos voltados para o usuário. Muitas vezes, isso é usado para recuperar dados que não deveriam estar acessíveis ou para excluir ou desmontar informações no banco de dados.
  • ORM: Os ORMs, ou mapeadores objeto-relacionais, são camadas de abstração que traduzem entre as representações de dados usadas em bancos de dados relacionais e a representação na memória usada com programação orientada a objetos. O ORM fornece uma interface orientada a objetos para dados dentro do banco de dados, tentando reduzir a quantidade de código e usar arquétipos familiares para acelerar o desenvolvimento.
  • Incompatibilidade de impedância objeto-relacional: A incompatibilidade de impedância objeto-relacional refere-se à dificuldade de traduzir entre um aplicativo orientado a objeto e um banco de dados relacional. Como as estruturas de dados variam significativamente, pode ser difícil alterar e transcrever fielmente e com desempenho as estruturas de dados programáticas para o formato usado pelo back-end de armazenamento.
  • Estrutura de persistência: Uma estrutura de persistência é uma camada de abstração de middleware desenvolvida para preencher a lacuna entre os dados do programa e os bancos de dados. Os frameworks de persistência também podem ser ORMs se a abstração que eles empregam mapear objetos para entidades relacionais.
  • Criador de consultas: Um construtor de consultas é uma camada de abstração que ajuda os desenvolvedores a acessar e controlar bancos de dados fornecendo uma interface controlada que adiciona recursos de usabilidade, segurança ou flexibilidade. Normalmente, os construtores de consultas são relativamente leves, concentram-se em facilitar o acesso e a representação de dados e não tentam traduzir os dados em um paradigma de programação específico.
  • SQL: SQL, ou linguagem de consulta estruturada, é uma linguagem específica de domínio desenvolvida para gerenciar sistemas de gerenciamento de banco de dados relacional. Ele pode ser usado para consultar, definir e manipular dados em um banco de dados, bem como suas estruturas organizacionais. O SQL é onipresente entre os bancos de dados relacionais.


Conclusão


Neste artigo, analisamos algumas opções diferentes para fazer a interface com seu banco de dados a partir de seu aplicativo. Examinamos os diferentes níveis de abstração e a flexibilidade oferecida pelo uso de linguagens de consulta nativas de banco de dados, como SQL, usando um construtor de consultas para ajudar a criar consultas com segurança e ORMs para fornecer um nível de abstração mais completo.

Cada uma dessas abordagens tem seus usos e algumas podem ser mais adequadas para certos tipos de aplicações do que outras. É importante entender os requisitos do aplicativo, o conhecimento do banco de dados da sua organização e os custos das abstrações (ou falta delas) que você decide implementar. No geral, entender cada abordagem lhe dará a melhor chance de selecionar a opção que será mais adequada para seus projetos.