Quando devo usar o DBIx :: Class do Perl?


17

DBIx :: Class é uma interface Perl popular para qualquer banco de dados ao qual você possa se conectar através do DBI . Há uma boa documentação para seus detalhes técnicos, mas poucas informações sobre seu uso adequado (incluindo as situações em que você provavelmente não deseja).

Em muitas situações, as pessoas o buscam reflexivamente porque pensam que devem usá-lo para tudo o que envolve um banco de dados. No entanto, na maioria das vezes eu o vi mal a ponto de se tornar um ponto problemático. Minha pergunta durante as revisões de código e arquitetura é sempre "Qual benefício o Foo está oferecendo a você?" Na maioria das vezes, os desenvolvedores que vejo nessas situações não conseguem formar uma resposta coerente para isso. Mas eles também não entendem o SQL simples.

Nos últimos meses, tenho perguntado às pessoas "Por que você usa DBIx :: Class?" e recebeu apenas uma boa resposta (e essa pessoa também foi capaz de responder ao acompanhamento "Quando você não a usaria"). Peter Rabbitson, o desenvolvedor principal, chegou perto de uma resposta em sua entrevista no FLOSS Weekly , mas está um pouco enterrado no meio da entrevista.

Então, como posso decidir se o DBIx :: Class é apropriado para um projeto?


3
Você está escrevendo outro livro? :)
simbabque

1
Na minha experiência, a dor surge quando o DBIC é usado em situações em que é exagero. E, apesar de muito poderoso, muitas vezes é um exagero, porque as pessoas usam apenas os recursos básicos (geração e junções de SQL) e não precisam de mais nada. Foi por isso que escrevi DBIx :: Lite, que fornece esses recursos básicos e não requer que nenhum esquema seja codificado.
Alessandro

Respostas:


24

Antes de responder à pergunta, acho que alguns antecedentes estão em ordem.

O núcleo do problema

Após anos entrevistando e contratando desenvolvedores, aprendi duas coisas:

  1. A grande maioria dos desenvolvedores tem muito pouca experiência em design de banco de dados.

  2. Percebi uma correlação fraca entre aqueles que não entendem bancos de dados e aqueles que odeiam ORMs.

(Nota: e sim, eu sei que existem aqueles que entendem muito bem os bancos de dados que odeiam ORMs)

Quando as pessoas não entendem por que as chaves estrangeiras são importantes, por que você não incorporar o nome do fabricante na itemmesa, ou por que customer.address1, customer.address2e customer.address3os campos não são uma boa idéia, acrescentando um ORM para torná-lo mais fácil para eles a erros de banco de dados de gravação não vai ajudar em nada.

Em vez disso, com um banco de dados projetado adequadamente e um caso de uso OLTP, os ORMs são dourados. A maior parte do trabalho árduo desaparece e, com ferramentas como DBIx :: Class :: Schema :: Loader , posso passar de um bom esquema de banco de dados para trabalhar com o código Perl em minutos. Eu citaria a Regra de Pareto e diria que 80% dos meus problemas foram resolvidos com 20% do trabalho, mas, na realidade, acho os benefícios ainda maiores que isso.

Abusando da solução

Outra razão pela qual algumas pessoas odeiam ORMs é porque elas deixam a abstração vazar. Vamos considerar o caso comum dos aplicativos da web MVC. Aqui está algo que geralmente vemos (pseudo-código):

GET '/countries/offices/$company' => sub {
    my ( $app, $company_slug ) = @_;
    my $company = $app->model('Company')->find({ slug => $company_slug }) 
      or $app->redirect('/');
    my $countries = $app->model('Countries')->search(
     {
         'company.company_id' => $company->company_id,
     },
     {
         join     => [ offices => 'company' ],
         order_by => 'me.name',
     },
   );
   $app->stash({
     company   => $company,
     countries => $country,
   });
}

As pessoas escrevem rotas de controladores assim e dão tapinhas nas costas, achando que é um código bom e limpo. Eles ficariam horrorizados com a codificação do SQL em seus controladores, mas fizeram pouco mais do que expor uma sintaxe SQL diferente. Seu código ORM precisa ser inserido em um modelo e eles podem fazer isso:

GET '/countries/offices/$company' => sub {
   my ( $app, $company_slug ) = @_;
   my $result = $app->model('Company')->countries($company_slug)
     or $app->redirect('/');
   $app->stash({ result => $result });
}

Você sabe o que aconteceu agora? Você encapsulou seu modelo corretamente, não expôs o ORM e, mais tarde, quando descobre que pode buscar esses dados em um cache em vez do banco de dados, não precisa alterar o código do controlador (e é mais fácil para escrever testes e reutilizar a lógica).

Na realidade, o que acontece é que as pessoas vazam seu código ORM em todos os seus controladores (e visualizações) e, quando atingem problemas de escalabilidade, começam a culpar o ORM em vez de sua arquitetura. O ORM tem uma má reputação (vejo isso repetidamente para muitos clientes). Em vez disso, oculte essa abstração para que, quando você realmente atingir os limites do ORM, possa escolher as soluções apropriadas para o seu problema, em vez de permitir que o código seja tão fortemente acoplado ao ORM que você estará atrapalhado.

Relatórios e outras limitações

Como Rob Kinyon deixou claro acima, os relatórios tendem a ser uma fraqueza nos ORMs. Esse é um subconjunto de um problema maior, no qual SQL ou SQL complicado, que abrange várias tabelas, às vezes não funciona bem com ORMs. Por exemplo, às vezes o ORM força um tipo de junção que não quero e não sei como consertar isso. Ou talvez eu queira usar uma dica de índice no MySQL, mas não é fácil . Ou, às vezes, o SQL é tão complicado que seria melhor escrever o SQL do que a abstração fornecida.

Isso é parte do motivo pelo qual comecei a escrever DBIx :: Class :: Report . Até agora, funciona bem e resolve a maioria dos problemas que as pessoas têm aqui (desde que estejam bem com uma interface somente leitura). E, embora pareça uma muleta, na realidade, desde que você não esteja perdendo sua abstração (como explicado na seção anterior), isso facilita DBIx::Classainda mais o trabalho.

Então, quando eu escolheria DBIx :: Class?

Para mim, eu o escolhia na maioria das vezes em que preciso de uma interface para um banco de dados. Eu tenho usado por anos. No entanto, talvez eu não o escolha para um sistema OLAP, e os programadores mais recentes certamente terão dificuldades com isso. Além disso, muitas vezes acho que preciso de metaprogramação e, embora DBIx::Classofereça as ferramentas, elas são muito mal documentadas.

A chave para o uso DBIx::Classcorreto é a mesma da maioria dos ORMs:

  1. Não vaze a abstração.

  2. Escreva seus malditos testes.

  3. Saiba como descer para o SQL, conforme necessário.

  4. Aprenda a normalizar um banco de dados.

DBIx::Class, depois que você aprender, cuidará da maior parte do seu trabalho pesado e facilitará a criação rápida de aplicativos.


1
Talvez você possa adicionar outra lista para quando não a usaria. :)
Brian d foy

1
Isso é óbvio para você, mas provavelmente não é óbvio para muitos leitores (digo, depois de passar anos #dbix-classe #catalyst) - a chave para a parte "não vaze a abstração" é que tudo o que você está trabalhando no DBIC é uma subclasse de algo que fornece o comportamento do cortador de biscoitos. Você é fortemente encorajado a adicionar métodos às suas subclasses e, a menos que esteja fazendo um trabalho de Q&D, apenas os métodos que você escreveu devem fazer parte da sua interface pública.
Hbbs

@ Hobbs: De fato, é aí que vejo as pessoas errarem mais com isso e é assim que ficam presas ao DBIC. Geralmente assumimos que as pessoas sabem o que estão fazendo no pequeno, mas descobrem no grande que não sabem.
Brian d foy

9

Para saber quando usar algo, é importante entender qual é o objetivo da coisa. Qual é o objetivo do produto.

DBIx :: Class é um ORM - Mapeador Objeto-Relacional. Um ORM pega as estruturas de dados do banco de dados relacional baseado em conjunto / relacional e mapeia-o para uma árvore de objetos. O mapeamento tradicional é um objeto por linha, usando a estrutura da tabela como uma descrição da classe. Os relacionamentos pai-filho no banco de dados são tratados como relacionamentos de contenção entre objetos.

Esses são os ossos disso. Mas isso não ajuda a decidir se você deve usar um ORM. ORMs são úteis principalmente quando o seguinte é verdadeiro:

  • Você usa um banco de dados relacional.
  • Seus dados são amplamente utilizados para OLTP (Online Transaction Processing).
  • Você não escreve relatórios no seu aplicativo.

A maior força que um ORM possui é a construção de um bom SQL para percorrer um gráfico em árvore sobreposto à estrutura de dados relacionais. O SQL geralmente é cabeludo e complexo, mas esse é o preço do gerenciamento da incompatibilidade de impedâncias.

Embora os ORMs sejam muito bons em escrever SQL de extração de linhas, eles são muito ruins em escrever agrupamento de SQL. Esse é o tipo de SQL em que os relatórios são criados. Esse tipo de agrupamento é criado usando ferramentas diferentes, não um ORM.

Existem muitos ORMs em vários idiomas, vários no Perl. Outros mapeadores são Class :: DBI e Rose :: DB. DBIx :: Class é frequentemente considerado melhor do que os outros, principalmente pela força de seus conjuntos de resultados. Este é um conceito em que a geração do SQL é separada da execução do SQL.


Atualização : em resposta ao Ovid, o DBIx :: Class (por meio do SQL :: Abstract) fornece a capacidade de especificar as colunas a serem retornadas e as dicas de índice a serem usadas.

No entanto, em geral, se você deseja fazer isso, é melhor não usar um ORM para essa consulta específica. Lembre-se - o objetivo principal de um ORM é mapear linhas em uma tabela para objetos de uma classe cujos atributos são as colunas da tabela. Se você estiver preenchendo apenas alguns dos atributos, os usuários em potencial desse objeto não saberão quais atributos estão preenchidos ou não. Isso leva a uma terrível programação defensiva e / ou a um ódio geral às ORMs.

Quase sempre, o desejo de usar dicas de índice ou limitar quais colunas são retornadas é uma otimização de velocidade e / ou uma consulta agregada.

  • As consultas de agregação são o caso em que os ORMs NÃO são projetados. Embora o DBIx :: Class possa criar consultas agregadas, você não está criando um gráfico de objeto; portanto, use DBI diretamente.
  • Otimizações de desempenho são usadas porque os dados que estão sendo consultados são muito grandes para o banco de dados subjacente, independentemente de como você os acessa. A maioria dos bancos de dados relacionais é ideal para tabelas com linhas de até 1-3MM executando SSDs, onde a maioria dos dados + índices se encaixa na RAM. Se sua situação for maior que isso, todos os bancos de dados relacionais terão problemas.

Sim, um ótimo DBA pode criar tabelas com mais de 100MM de linhas no Oracle ou SQL * Server. Se você está lendo isso, não possui um ótimo DBA na equipe.

Tudo isso dito, um bom ORM faz mais do que apenas criar gráficos de objetos - ele também fornece uma definição introspectável do seu banco de dados. Você pode usar isso para ajudar a criar consultas ad-hoc e consumi-las como faria com o DBI, sem criar o gráfico de objeto.


Acho que quase tudo o que vejo grava relatórios, e é provavelmente por isso que as pessoas precisam passar para consultas manuais (e essa é a dor).
Brian d foy #

1
Não entendo por que você precisa ir para consultas manuais para relatórios. Eu criei alguns relatórios bastante complexos usando DBIC. Obviamente, isso geralmente envolve a criação de um enorme conjunto de resultados personalizado com o uso intenso de 'pré-busca' e 'junção'.
Dave Cross

Dave: o SQL manual pode ser muito mais fácil de escrever e garantir que você apenas puxe os sete campos necessários de três tabelas e os represente em uma única linha. Além disso, é muito mais fácil fornecer dicas ao escrever SQL bruto.
Curtis Poe #

> para garantir que você apenas puxe os sete campos necessários Sim, é para isso que as "colunas" atraem nas pesquisas do ResultSet. Os únicos argumentos válidos que ouvi ou vi para executar SQL bruto são: 1. Subconsultas extremamente complexas, que geralmente são o produto de uma tabela / banco de dados mal projetada 2. Execução de DDL ou outras operações 'do', que DBIC não é realmente construído para. 3. Tentando invadir as dicas do índice. Novamente, isso é mais uma fraqueza do RDBMS, mas às vezes precisa ser feito. E é possível adicionar esse tipo de funcionalidade ao DBIC.
Brendan Byrd

8

Como um dos principais desenvolvedores da plataforma de comércio eletrônico Interchange6 (e a principal motosserra de esquema), tenho uma experiência bastante profunda com o DBIC. Aqui estão algumas das coisas que a tornam uma ótima plataforma:

  • O gerador de consultas permite que você escreva uma vez para muitos mecanismos de banco de dados (e várias versões de cada). Atualmente, oferecemos suporte ao Interchange6 com MySQL, PostgreSQL e SQLite e adicionaremos suporte a mais mecanismos assim que tivermos tempo e recursos. Atualmente, existem apenas dois caminhos de código em todo o projeto que possuem código adicional para atender às diferenças entre os mecanismos, e isso se deve exclusivamente à falta de uma função específica do banco de dados (o SQLite está faltando em alguns locais) ou devido à idiotice do MySQL que mudou a maneira como sua função LEAST lida com NULLs entre duas versões secundárias.

  • Consultas predefinidas significam que eu posso criar métodos simples que podem ser chamados (com ou sem argumentos) a partir do código do aplicativo, para manter minhas consultas principalmente dentro da definição do esquema, em vez de desarrumar o código do aplicativo.

  • A geração de consultas compostas permite que as consultas sejam divididas em pequenas consultas predefinidas compreensíveis e depois encadeadas para criar consultas complexas que seriam difíceis de manter a longo prazo no DBIC (ainda pior no SQL puro) se fossem construídas em uma única etapa.

  • Esquema :: Loader nos permitiu usar o DBIC com aplicativos herdados, oferecendo uma nova vida útil e um caminho muito mais simples para o futuro.

  • Todos os plugins DBIC, DeploymentHandler e Migração aumentam imensamente o conjunto de ferramentas que simplificam minha vida.

Uma das grandes diferenças entre o DBIC e a maioria das outras plataformas do tipo ORM / ORM é que, embora tente guiá-lo em sua maneira de fazer as coisas, também não o impede de fazer coisas loucas que você gosta:

  • Você pode usar funções SQL e procedimentos armazenados que o DBIC não conhece apenas fornecendo o nome da função como uma chave na consulta (também pode ser divertido quando você tenta usar LEAST no MySQL ^^, mas isso não é culpa do DBIC) .

  • O SQL literal pode ser usado quando não há uma 'maneira DBIC' de fazer alguma coisa e o resultado retornado ainda é agrupado em boas classes com os acessadores.

TL; DR Eu provavelmente não me incomodaria em usá-lo para aplicativos realmente simples, com apenas algumas tabelas, mas quando eu precisar gerenciar algo mais complexo, especialmente onde a compatibilidade entre mecanismos e a manutenção de longo prazo são fundamentais, o DBIC é geralmente meu caminho preferido.


7

(Antes de começar, devo dizer que isso apenas compara os wrappers DBI, DBI e DBI baseados em Mojo. Não tenho experiência com nenhum outro Perl ORM e, portanto, não os comentarei diretamente).

DBIC faz muitas coisas muito bem. Não sou muito usuário, mas conheço a teoria. Ele faz um bom trabalho de geração de SQL e, especialmente (como já foi dito), manipula junções, etc.

A maior vantagem que vejo é a capacidade de usar DIRETAMENTE os resultados como sua classe de modelo. Isso também é conhecido como "adição de métodos do conjunto de resultados", em que você pode obter seus resultados e chamar métodos sobre esses resultados. O exemplo comum é buscar um objeto de usuário no DBIC e, em seguida, chamar um método para verificar se a senha é válida.

A implantação do esquema pode ser difícil, mas sempre difícil. O DBIC possui ferramentas (algumas em módulos externos) que facilitam e provavelmente são mais fáceis do que gerenciar seus próprios esquemas manualmente.

Do outro lado da moeda, existem outras ferramentas que atraem outras sensibilidades, como os invólucros DBI com sabor mojo. Eles têm o apelo de serem magros e ainda assim úteis. A maioria também adotou uma sugestão do Mojo :: Pg (o original) e adicionou recursos úteis, como gerenciamento de esquema na integração de arquivos simples e pubsub.

Esses módulos com sabor Mojo surgiram de um outro ponto fraco do DBIC, que é que ele ainda não é capaz de fazer consultas assíncronas. Os autores garantiram-me que é tecnicamente possível, talvez até rapidamente, mas há problemas ao projetar uma API que seria adequada. (É certo que até me pediram para ajudar com isso e, enquanto o faria, simplesmente não sei como mover a agulha no tempo que tenho que dedicar a ela).

TL; DR usa DBIC, a menos que você goste de SQL ou precise de assíncrono; nesse caso, investigue os wrappers DBI com sabor de Mojo.


6

Eu escrevi meus pensamentos sobre isso no DBIC vs DBI há três anos. Para resumir, listei dois motivos principais:

  1. DBIC significa que não preciso pensar em todo o SQL trivial necessário para praticamente qualquer aplicativo que escrevo.
  2. DBIC me devolve objetos do banco de dados, em vez de estruturas de dados idiotas. Isso significa que eu tenho toda a bondade OO padrão para brincar. Em particular, acho realmente útil poder adicionar métodos aos meus objetos DBIC.

Quanto aos antipadrões, bem, o único que consigo pensar é no desempenho. Se você realmente deseja extrair todos os ciclos de clock da CPU, talvez o DBIC não seja a ferramenta certa para o trabalho. Mas, certamente para os aplicativos que escrevem, esses casos são cada vez mais raros. Não me lembro da última vez que escrevi um novo aplicativo que falava com um banco de dados e não usava DBIC. Obviamente, ajuda se você souber um pouco sobre o ajuste das consultas que o DBIC gera.


2
Não consigo corrigir erros de digitação porque não altero caracteres suficientes ("ferramenta correta"). Curiosamente coxo. Mas esse é o tipo de resposta que me intriga. Acho que no seu post do PerlHacks, você aborda uma coisa que Rob aponta, mas não considera a outra. Em muitos casos, encontrei pessoas voltando ao SQL manual.
Brian d foy #

1

Do jeito que eu faço escala:

  1. crie uma classe que forneça o construtor de soquetes DBI e os métodos de teste.

  2. derivar essa classe em suas classes de consulta SQL (uma classe por tabela sql) e testar o soquete no momento do construtor.

  3. use variáveis ​​com escopo de classe para manter o nome da tabela e os nomes das colunas do índice primário.

  4. Escreva todo o nome da tabela de interpolação SQL e a coluna do índice primário dessas variáveis ​​em vez de defini-las estaticamente no SQL.

  5. use macros de editor para permitir que você faça pares básicos de métodos DBI (prepare e execute) enquanto digita SOMENTE a instrução sql.

Se você puder fazer isso, poderá escrever um código de API limpo sobre o DBI o dia todo com relativa facilidade.

O que você encontrará é que muitas de suas consultas serão portáveis ​​em várias tabelas. Nesse ponto, você pode recortar e colar em uma classe EXPORTTER e espalhá-las de volta quando necessário. É aqui que entra em jogo a interpolação com escopo de classe do nome da tabela e dos nomes das colunas principais do índice.

Eu usei essa abordagem para dimensionar centenas de métodos DBI com desempenho relativamente bom. Eu não gostaria de tentar manter o código DBI de qualquer outra maneira.

Quando usar o DBI: Sempre.

Eu não acho que essa foi sua pergunta real. Sua verdadeira pergunta era: "Parece uma enorme PITA, por favor, diga-me que não preciso fazer isso?"

Você não Faça o layout correto e a parte do DBI fica redundante o suficiente para poder automatizá-lo.


Alguma chance de você ter um projeto de código aberto que possa compartilhar, ou talvez apenas uma essência no github com um exemplo de cada classe? Acho que as idéias que você está dizendo são interessantes e provavelmente seriam viáveis ​​para projetos de muitas pessoas, mas seria um pouco mais fácil seguir alguns exemplos.
msouth 26/07

0

Não sou especialista em Perl, mas uso muito isso. Há muitas coisas que eu não sei ou sei como fazer melhor; algumas coisas que ainda não sou capaz de entender, apesar da documentação.

Costumo começar com o DBI porque penso: "Oh, este é um projeto simples, não preciso do inchaço de um ORM e não quero incomodar os módulos de instalação e esquema". Mas muito rapidamente - quase sempre - começo rapidamente a me amaldiçoar por essa decisão. Quando quero começar a ser criativo em minhas consultas SQL (consultas dinâmicas, não apenas espaços reservados de comparação), luto para manter a sanidade usando o DBI. SQL :: Abstract ajuda muito e, geralmente, isso é provavelmente suficiente para mim. Mas minha próxima luta mental é manter tanto SQL dentro do meu código. É muito perturbador para mim ter linhas e linhas de SQL incorporadas dentro de heredocs feios. Talvez eu precise usar um IDE de qualidade.

No final, mais vezes do que não, fico com o DBI direto. Mas continuo desejando que houvesse uma maneira melhor. O DBIx :: Class tem algumas características realmente interessantes e eu o usei algumas vezes, mas parece um exagero para todos, exceto os maiores projetos. Não tenho certeza do que acho mais complicado de gerenciar: DBI com SQL disperso ou DBIC com módulos de esquema disperso.

(Oh, coisas como restrições e gatilhos são uma enorme vantagem para mim no DBIC.)


4
Esta resposta não chega ao ponto muito bem - claro, você tem problemas ao usar o DBIx, mas por que você tem esses problemas? O DBI não é flexível, muito acoplado ao DB, não é escalável ou o quê?
Jay Elston
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.