Quand dois-je utiliser DBIx :: Class de Perl?

17

DBIx :: Class est une interface Perl populaire vers n'importe quelle base de données à laquelle vous pouvez vous connecter via DBI . Il y a une bonne documentation pour ses détails techniques, mais peu d'informations sur sa bonne utilisation (y compris les situations où vous ne le voulez probablement pas).

Dans de nombreuses situations, les gens y parviennent par réflexe car ils pensent qu'ils devraient l'utiliser pour tout ce qui implique une base de données. Cependant, je l'ai vu le plus souvent mal utilisé au point où il devient un point douloureux. Ma question lors des revues de code et d'architecture est toujours "Quel avantage Foo vous apporte-t-il?" Le plus souvent, les développeurs que je vois dans ces situations ne peuvent pas y répondre de manière cohérente. Mais souvent, ils ne comprennent pas non plus le SQL simple.

Au cours des derniers mois, j'ai demandé aux gens "Pourquoi utilisez-vous DBIx :: Class?" et n'ont reçu qu'une seule bonne réponse (et cette personne a également pu répondre au suivi "Quand ne l'utiliseriez-vous pas"). Peter Rabbitson, le développeur principal, s'est approché d'une réponse dans son interview sur FLOSS Weekly , mais c'est un peu enterré au milieu de l'interview.

Alors, comment puis-je décider si l'utilisation de DBIx :: Class est appropriée pour un projet?

brian d foy
la source
3
Ecrivez-vous un autre livre? :)
simbabque
1
D'après mon expérience, la douleur survient lorsque DBIC est utilisé dans des situations où il est exagéré. Et, bien qu'il soit très puissant, il est souvent exagéré car les gens n'utilisent que les fonctionnalités de base (génération SQL et jointures) et n'ont besoin de rien d'autre. C'est pourquoi j'ai écrit DBIx :: Lite, qui fournit ces fonctionnalités de base et ne nécessite aucun schéma pour être codé en dur.
Alessandro

Réponses:

24

Avant de répondre à la question, je pense que certains antécédents s'imposent.

Le cœur du problème

Après des années d'interview et d'embauche de développeurs, j'ai appris deux choses:

  1. La grande majorité des développeurs ont très peu d'expérience dans la conception de bases de données.

  2. J'ai remarqué une faible corrélation entre ceux qui ne comprennent pas les bases de données et ceux qui détestent les ORM.

(Remarque: et oui, je sais qu'il y a ceux qui comprennent très bien les bases de données qui détestent les ORM)

Quand les gens ne comprennent pas pourquoi les clés étrangères sont importantes, pourquoi vous ne pas inclure le nom du fabricant dans le itemtableau, ou pourquoi customer.address1, customer.address2et les customer.address3champs ne sont pas une bonne idée, en ajoutant une ORM pour le rendre plus facile pour eux de bogues de base de données d'écriture ne va rien aider.

Au lieu de cela, avec une base de données correctement conçue et un cas d'utilisation OLTP, les ORM sont dorés. La plupart du travail de grognement disparaît et avec des outils tels que DBIx :: Class :: Schema :: Loader , je peux passer d'un bon schéma de base de données à du code Perl fonctionnel en quelques minutes. Je voudrais citer la règle de Pareto et dire que 80% de mes problèmes ont été résolus avec 20% du travail, mais en réalité, je trouve les avantages encore plus importants que cela.

Abuser de la solution

Une autre raison pour laquelle certaines personnes détestent les ORM est qu'elles laisseront l'abstraction s'échapper. Prenons le cas commun des applications Web MVC. Voici quelque chose que nous voyons couramment (pseudo-code):

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,
   });
}

Les gens écrivent des routes de contrôleur comme ça et se tapent dans le dos, pensant que c'est du bon code propre. Ils seraient consternés par le codage en dur de SQL dans leurs contrôleurs, mais ils n'ont guère fait plus qu'exposer une syntaxe SQL différente. Leur code ORM doit être inséré dans un modèle, puis ils peuvent le faire:

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

Tu sais ce qui s'est passé maintenant? Vous avez correctement encapsulé votre modèle, vous n'avez pas exposé l'ORM, et plus tard, lorsque vous constatez que vous pouvez récupérer ces données dans un cache au lieu de la base de données, vous n'avez pas besoin de changer le code de votre contrôleur (et c'est plus facile écrire des tests et réutiliser la logique).

En réalité, ce qui se passe, c'est que les gens fuient leur code ORM sur tous leurs contrôleurs (et vues) et lorsqu'ils rencontrent des problèmes d'évolutivité, ils commencent à blâmer l'ORM plutôt que leur architecture. L'ORM obtient un mauvais coup sec (je vois cela à plusieurs reprises pour de nombreux clients). Au lieu de cela, masquez cette abstraction afin que lorsque vous avez véritablement atteint les limites de l'ORM, vous pouvez choisir des solutions appropriées à votre problème plutôt que de laisser le code être si étroitement couplé à l'ORM que vous êtes lié au porc.

Rapports et autres limitations

Comme Rob Kinyon l'a clairement expliqué ci-dessus, les rapports ont tendance à être une faiblesse des ORM. Il s'agit d'un sous-ensemble d'un problème plus vaste où le SQL complexe ou SQL qui s'étend sur plusieurs tables ne fonctionne parfois pas bien avec les ORM. Par exemple, parfois l'ORM force un type de jointure que je ne veux pas et je ne peux pas dire comment résoudre ce problème. Ou peut-être que je veux utiliser un indice dans MySQL, mais ce n'est pas facile . Ou parfois, le SQL est tellement compliqué qu'il serait préférable d'écrire le SQL plutôt que l'abstraction fournie.

C'est une des raisons pour lesquelles j'ai commencé à écrire DBIx :: Class :: Report . Jusqu'à présent, cela fonctionne bien et résout la majorité des problèmes que les gens ont ici (tant qu'ils sont OK avec une interface en lecture seule). Et bien que cela ressemble à une béquille, en réalité, tant que vous ne fuyez pas votre abstraction (comme expliqué dans la section précédente), cela rend le travail DBIx::Classencore plus facile.

Alors, quand choisirais-je DBIx :: Class?

Pour moi, je choisirais la plupart du temps que j'ai besoin d'une interface avec une base de données. Je l'utilise depuis des années. Cependant, je ne le choisirai peut-être pas pour un système OLAP, et les nouveaux programmeurs vont certainement avoir du mal avec. De plus, je trouve souvent que j'ai besoin de méta-programmation et bien que DBIx::Classfournit les outils, ils sont très mal documentés.

La clé d'une utilisation DBIx::Classcorrecte est la même que pour la plupart des ORM:

  1. Ne laissez pas fuir l'abstraction.

  2. Écrivez vos damnés tests.

  3. Apprenez à passer à SQL, au besoin.

  4. Apprenez à normaliser une base de données.

DBIx::Class, une fois que vous l'aurez appris, s'occupera de la plupart de vos tâches lourdes pour vous et facilitera l'écriture rapide des applications.

Curtis Poe
la source
1
Vous pouvez peut-être ajouter une autre liste lorsque vous ne souhaitez pas l'utiliser. :)
brian d foy
1
Cela est évident pour vous, mais probablement pas évident pour de nombreux lecteurs (je dis, après avoir passé des années dans #dbix-classet #catalyst) - la clé du bit "ne pas divulguer l'abstraction" est que chaque chose avec laquelle vous travaillez dans DBIC est une sous-classe de quelque chose qui fournit le comportement de l'emporte-pièce. Vous êtes fortement encouragé à ajouter des méthodes à vos sous-classes, et à moins que vous ne fassiez un travail Q&D, seules les méthodes que vous avez écrites devraient faire partie de votre interface publique.
Hobbs
@hobbs: En effet, c'est là que je vois que les gens se trompent le plus et c'est ainsi qu'ils se retrouvent coincés avec DBIC. Nous supposons souvent que les gens savent ce qu'ils font dans le petit, mais découvrent dans le grand qu'ils ne le font pas.
brian d foy
9

Pour savoir quand utiliser quelque chose, il est important de comprendre quel est le but de la chose. Quel est l'objectif du produit.

DBIx :: Class est un ORM - Object-Relational Mapper. Un ORM prend les structures de données de la base de données relationnelle basée sur un ensemble relationnel et les mappe à une arborescence d'objets. Le mappage traditionnel est un objet par ligne, en utilisant la structure de la table comme description de classe. Les relations parent-enfant dans la base de données sont traitées comme des relations de confinement entre les objets.

Voilà les os de celui-ci. Mais cela ne vous aide pas à décider si vous devez utiliser un ORM. Les ORM sont principalement utiles lorsque les conditions suivantes sont remplies:

  • Vous utilisez une base de données relationnelle.
  • Vos données sont largement utilisées pour OLTP (Online Transaction Processing).
  • Vous n'écrivez pas de rapports dans votre application.

La plus grande force d'un ORM est de construire un bon SQL pour parcourir un graphique arborescent superposé à la structure des données relationnelles. Le SQL est souvent poilu et complexe, mais c'est le prix de la gestion de l'inadéquation d'impédance.

Bien que les ORM soient très bons pour écrire du SQL d'extraction de lignes, ils sont très mauvais pour écrire du SQL de classement. Il s'agit du type de SQL sur lequel les rapports sont construits. Ce type de classement est construit en utilisant différents outils, pas un ORM.

Il existe de nombreux ORM dans différentes langues, plusieurs en Perl. Les autres mappeurs sont Class :: DBI et Rose :: DB. DBIx :: Class est souvent considéré comme meilleur que les autres, en grande partie en raison de ses ensembles de résultats. Il s'agit d'un concept où la génération SQL est séparée de l'exécution SQL.


Mise à jour : En réponse à Ovid, DBIx :: Class (via SQL :: Abstract) offre la possibilité de spécifier à la fois les colonnes à renvoyer et les indices d'index à utiliser.

En général, cependant, si vous souhaitez le faire, il vaut mieux ne pas utiliser d'ORM pour cette requête spécifique. Rappelez-vous - le but principal d'un ORM est de mapper des lignes dans une table avec des objets d'une classe dont les attributs sont les colonnes de la table. Si vous ne remplissez que certains des attributs, les utilisateurs potentiels de cet objet ne sauront pas quels attributs sont remplis ou non. Cela conduit à une horrible programmation défensive et / ou à une haine générale des ORM.

Presque toujours, le désir d'utiliser des indices d'index ou de limiter les colonnes retournées est soit une optimisation de la vitesse et / ou une requête d'agrégation.

  • Les requêtes d'agrégation sont le cas d'utilisation Les ORM ne sont PAS conçus pour. Bien que DBIx :: Class puisse créer des requêtes d'agrégation, vous ne créez pas de graphique d'objet, utilisez simplement DBI directement.
  • Les optimisations de performances sont utilisées car les données interrogées sont trop volumineuses pour la base de données sous-jacente, quelle que soit la façon dont vous y accédez. La plupart des bases de données relationnelles sont idéales pour les tables comptant jusqu'à 1 à 3 millions de lignes fonctionnant à partir de disques SSD où la plupart des données + indices tiennent dans la RAM. Si votre situation est plus grande que cela, alors chaque base de données relationnelle aura des problèmes.

Oui, un excellent DBA peut faire fonctionner des tables avec 100MM + de lignes dans Oracle ou SQL * Server. Si vous lisez ceci, vous n'avez pas un excellent DBA sur le personnel.

Tout cela dit, un bon ORM fait plus que simplement créer des graphiques d'objets - il fournit également une définition introspectible de votre base de données. Vous pouvez l'utiliser pour créer des requêtes ad hoc et les utiliser comme vous le feriez avec DBI, sans créer le graphique d'objet.

Rob Kinyon
la source
Je pense que presque tout ce que je vois écrit des rapports, ce qui explique probablement pourquoi les gens doivent passer aux requêtes manuelles (et c'est la douleur).
brian d foy
1
Je ne comprends pas pourquoi vous devez passer aux requêtes manuelles de rapports. J'ai construit des rapports assez complexes en utilisant DBIC. Bien sûr, cela implique souvent de créer un ensemble de résultats personnalisé massif avec une utilisation intensive de «prefetch» ​​et «join».
Dave Cross
Dave: le SQL manuel peut être beaucoup plus facile à écrire et vous assurer de ne tirer que les sept champs dont vous avez besoin de trois tables et de les représenter sur une seule ligne. De plus, il est beaucoup plus facile de fournir des conseils lors de l'écriture de SQL brut.
Curtis Poe
> pour vous assurer que vous ne tirez que sur les sept champs dont vous avez besoin Oui, c'est à cela que sert l'attr "colonnes" pour les recherches ResultSet. Les seuls arguments valides que j'ai entendus ou vus pour faire du SQL brut sont: 1. des sous-requêtes extrêmement complexes, qui sont généralement le produit d'une table / base de données mal conçue; pas vraiment construit pour. 3. Essayer de pirater les indices. Encore une fois, c'est plus une faiblesse du SGBDR, mais parfois cela doit être fait. Et il est possible d'ajouter simplement ce type de fonctionnalité dans DBIC.
Brendan Byrd
8

En tant que l'un des principaux développeurs de la plate-forme de commerce électronique Interchange6 (et de la tronçonneuse de schéma principale), j'ai une expérience assez approfondie avec DBIC. Voici quelques-unes des caractéristiques qui en font une excellente plateforme:

  • Le générateur de requêtes vous permet d'écrire une fois pour de nombreux moteurs de base de données (et plusieurs versions de chacun). Nous prenons actuellement en charge Interchange6 avec MySQL, PostgreSQL et SQLite et ajouterons la prise en charge de plus de moteurs une fois que nous aurons le temps et les ressources. Il n'y a actuellement que deux chemins de code dans l'ensemble du projet qui ont du code supplémentaire pour tenir compte des différences entre les moteurs et cela est uniquement dû au manque d'une fonction de base de données spécifique (SQLite manque par endroits) ou à l'idiotie de MySQL qui a changé la façon dont sa fonction LEAST gère les valeurs NULL entre deux versions mineures.

  • Les requêtes prédéfinies signifient que je peux créer des méthodes simples qui peuvent être appelées (avec ou sans arguments) à partir du code d'application, donc je garde mes requêtes principalement dans la définition du schéma au lieu de jeter mon code d'application.

  • La génération de requêtes composables permet de diviser les requêtes en petites requêtes prédéfinies compréhensibles, puis de les enchaîner pour créer des requêtes complexes qui seraient difficiles à maintenir à long terme dans l'un ou l'autre DBIC (pire encore en SQL pur) si elles étaient construites en une seule étape.

  • Schema :: Loader nous a permis d'utiliser DBIC avec des applications héritées, donnant une nouvelle vie et un chemin beaucoup plus simple vers l'avenir.

  • Les plugins DBIC, DeploymentHandler et Migration ajoutent tous énormément à l'ensemble d'outils qui me simplifient la vie.

L'une des énormes différences entre DBIC et la plupart des autres plates-formes de type ORM / ORM est que même s'il essaie de vous guider dans sa façon de faire les choses, il ne vous empêche pas non plus de faire des trucs fous que vous aimez:

  • Vous pouvez utiliser des fonctions SQL et des procédures stockées que DBIC ne connaît pas simplement en fournissant le nom de la fonction comme clé dans la requête (peut également conduire à un certain amusement lorsque vous essayez d'utiliser LEAST dans MySQL ^^ mais ce n'est pas la faute de DBIC) .

  • Le littéral SQL peut être utilisé quand il n'y a pas de «méthode DBIC» pour faire quelque chose et que le résultat retourné est toujours emballé dans de belles classes avec des accesseurs.

TL; DR Je ne prendrais probablement pas la peine de l'utiliser pour des applications vraiment simples avec seulement quelques tables, mais lorsque je dois gérer quelque chose de plus complexe, en particulier lorsque la compatibilité entre les moteurs et la maintenabilité à long terme sont essentielles, alors DBIC est généralement mon chemin préféré.

SysPete
la source
7

(Avant de commencer, je dois dire que cela compare simplement les wrappers DBI basés sur DBIC, DBI et Mojo. Je n'ai aucune expérience avec d'autres ORM Perl et je ne les commenterai donc pas directement).

DBIC fait beaucoup de choses très bien. Je n'en suis pas un grand utilisateur, mais je connais la théorie. Il fait un très bon travail de génération SQL et surtout (comme on m'a dit) de gérer les jointures, etc. Il peut également très bien faire la prélecture d'autres données connexes.

Le principal avantage que je vois est la possibilité d'utiliser DIRECTEMENT les résultats comme classe de modèle. Ceci est autrement connu sous le nom d '"ajout de méthodes d'ensemble de résultats" dans lequel vous pouvez obtenir vos résultats et appeler des méthodes sur ces résultats. L'exemple courant consiste à récupérer un objet utilisateur à partir de DBIC, puis à appeler une méthode pour vérifier si leur mot de passe est valide.

Bien sûr, le déploiement de schéma peut être difficile, mais il est toujours difficile. DBIC possède des outils (certains dans des modules externes) qui le rendent plus facile, et probablement plus facile que de gérer vos propres schémas à la main.

De l'autre côté de la médaille, il existe d'autres outils qui font appel à d'autres sensibilités, comme les emballages DBI à saveur de mojo. Ceux-ci ont l'attrait d'être maigres et pourtant toujours utilisables. La plupart ont également pris exemple sur Mojo :: Pg (l'original) et ajouté des fonctionnalités pratiques telles que la gestion des schémas dans les fichiers plats et l'intégration de pubsub.

Ces modules à saveur Mojo sont nés d'un autre point faible de DBIC, à savoir qu'il n'est pas (encore) capable de faire des requêtes asynchrones. Les auteurs m'ont assuré que c'est techniquement possible, peut-être même rapidement, mais il y a des problèmes de conception d'une API qui conviendrait. (Certes, on m'a même demandé d'aider à cela, et bien que je le ferais, je ne sais tout simplement pas comment déplacer l'aiguille dans le temps que je dois y consacrer).

TL; DR utilisent DBIC à moins que vous n'aimiez SQL ou que vous ayez besoin d'async, auquel cas étudiez les wrappers DBI à saveur Mojo.

Joel Berger
la source
6

J'ai écrit mes réflexions à ce sujet dans DBIC vs DBI il y a trois ans. Pour résumer, j'ai énuméré deux raisons principales:

  1. DBIC signifie que je n'ai pas à penser à tout le SQL trivial qui est nécessaire pour à peu près n'importe quelle application que j'écris.
  2. DBIC me rend des objets de la base de données plutôt que des structures de données stupides. Cela signifie que j'ai toute la bonté OO standard pour jouer avec. En particulier, je trouve vraiment utile de pouvoir ajouter des méthodes à mes objets DBIC.

En ce qui concerne les anti-patterns, le seul auquel je puisse penser est la performance. Si vous voulez vraiment extraire chaque cycle d'horloge de votre CPU, alors DBIC n'est peut-être pas le bon outil pour le travail. Mais, certainement pour les applications qui écrivent, ces cas sont de plus en plus rares. Je ne me souviens pas de la dernière fois où j'ai écrit une nouvelle application qui parlait à une base de données et n'utilisait pas DBIC. Bien sûr, cela aide si vous en savez un peu sur le réglage des requêtes générées par DBIC.

Dave Cross
la source
2
Huh, je ne peux pas corriger les fautes de frappe parce que je ne change pas assez de caractères ("righ ttool"). Curieusement boiteux. Mais c'est le genre de réponse qui me laisse perplexe. Je pense que dans votre article PerlHacks, vous abordez une chose que Rob souligne, mais ne considérez pas l'autre. Dans de nombreux cas, j'ai trouvé des gens qui retournaient au SQL manuel.
brian d foy
1

La façon dont je le fais évoluer:

  1. créez une classe qui fournit le constructeur de socket DBI et les méthodes de test.

  2. dérivez cette classe dans vos classes de requête SQL (une classe par table sql) et testez le socket au moment du constructeur.

  3. utilisez des variables de portée de classe pour conserver le nom de votre table et les noms des colonnes d'index principal.

  4. Écrivez tous vos noms de table d'interpolation SQL et colonne d'index primaire à partir de ces variables au lieu de les définir statiquement dans SQL.

  5. utilisez des macros d'éditeur pour vous permettre de créer des paires de méthodes DBI de base (préparer et exécuter) tout en tapant UNIQUEMENT l'instruction sql.

Si vous pouvez le faire, vous pouvez écrire du code API propre au-dessus de DBI toute la journée avec une relative facilité.

Ce que vous trouverez, c'est que beaucoup de vos requêtes seront portables sur plusieurs tables. À ce stade, vous pouvez couper et coller dans une classe EXPORTER et les saupoudrer là où vous en avez besoin. C'est là que l'interpolation de portée de classe du nom de la table et des noms de colonne d'index principal entre en jeu.

J'ai utilisé cette approche pour évoluer vers des centaines de méthodes DBI avec des performances relativement bonnes. Je ne voudrais pas essayer de maintenir le code DBI d'une autre manière.

Quand utiliser le DBI: Toujours.

Je ne pense pas que ce soit votre vraie question. Votre vraie question était: "Cela ressemble à un énorme PITA, dites-moi s'il vous plaît que je n'ai pas à faire ça?"

Non. Disposez-le correctement et la partie DBI devient suffisamment redondante pour pouvoir l'automatiser principalement.

James Aanderson
la source
Y a-t-il une chance que vous ayez un projet open source que vous pourriez partager, ou peut-être même juste un aperçu sur github avec un exemple de chaque classe? Je pense que les idées que vous dites sont intéressantes et seraient probablement viables pour de nombreux projets, mais il serait un peu plus facile de commencer avec quelques exemples.
msouth
0

Je ne suis pas un expert Perl, mais je l'utilise beaucoup. Il y a beaucoup de choses que je ne sais pas ou que je peux faire mieux; certaines choses que je ne suis pas encore capable de comprendre, malgré la documentation.

J'ai tendance à commencer par DBI parce que je pense: "Oh, c'est un projet simple, je n'ai pas besoin du ballonnement d'un ORM et je ne veux pas me tracasser avec les modules de configuration et de schéma." Mais très rapidement - presque à chaque fois - je commence rapidement à me maudire pour cette décision. Lorsque je veux commencer à faire preuve de créativité dans mes requêtes SQL (requêtes dynamiques, et pas seulement les espaces réservés de comparaison), j'ai du mal à maintenir la raison à l'aide de DBI. SQL :: Abstract aide beaucoup, et généralement cela est probablement suffisant pour moi. Mais ma prochaine lutte mentale consiste à maintenir autant de SQL dans mon code. C'est très distrayant pour moi d'avoir des lignes et des lignes de SQL embarqué dans des heredocs moches. Peut-être que je dois utiliser un IDE de qualité.

Au final, plus souvent qu'autrement, je reste fidèle à DBI. Mais je souhaite toujours qu'il y ait une meilleure façon. DBIx :: Class a des caractéristiques vraiment intéressantes et je l'ai utilisé à quelques reprises, mais il semble tellement exagéré pour tous, sauf les plus gros projets. Je ne suis même pas sûr de ce que je trouve plus lourd à gérer: DBI avec SQL dispersé ou DBIC avec modules de schéma dispersés.

(Oh, des trucs comme les contraintes et les déclencheurs sont un énorme avantage pour DBIC.)

Stefan Adams
la source
4
Cette réponse ne vient pas très bien - bien sûr, vous avez des problèmes lorsque vous utilisez DBIx, mais pourquoi avez-vous ces problèmes? DBI n'est-il pas flexible, trop étroitement couplé à la DB, n'est-il pas évolutif, ou quoi?
Jay Elston