我什么时候应该使用Perl的DBIx :: Class?


17

DBIx :: Class是您可以通过DBI连接到的任何数据库的流行Perl接口。关于它的技术细节有很好的文档,但是关于它的正确使用(包括您可能不希望使用它的情况)的信息很少。

在许多情况下,人们反射性地实现它是因为他们认为应该将它用于涉及数据库的所有内容。但是,我经常看到它被滥用到令人痛苦的地步。我在代码和体系结构审查期间的问题始终是“ Foo给您带来什么好处?” 通常,在这种情况下,我看到的开发人员无法对此做出一致的回答。但是然后,他们也常常不了解简单的SQL。

在过去的几个月中,我一直在问人们“为什么使用DBIx :: Class?” 并且仅收到一个很好的答案(该人还能够回答后续问题“您何时不使用它”)。首席开发人员Peter Rabbitson在《FLOSS周刊》的访谈中得到了一个答案,但在访谈的中间有些隐瞒。

因此,如何确定使用DBIx :: Class是否适合项目?


3
你在写另一本书吗?:)
simbabque 2015年

1
以我的经验,当DBIC用于过度使用时会感到痛苦。而且,尽管功能非常强大,但由于人们只使用基本功能(SQL生成和联接),而无需其他任何功能,因此它通常会过分杀人。这就是为什么我写DBIx :: Lite的原因,它提供了这些基本功能,并且不需要任何模式进行硬编码。
亚历山德罗(Alessandro)2016年

Answers:


24

在我回答问题之前,我认为需要有一些背景知识。

问题的核心

经过多年的采访和雇用开发人员,我学到了两件事:

  1. 绝大多数开发人员在数据库设计方面经验很少。

  2. 我注意到那些不了解数据库的人和讨厌ORM的人之间存在松散的相关性。

(注意:是的,我知道有些人非常了解数据库,而讨厌ORM)

当人们不明白,为什么外键是很重要的,你为什么不中嵌入制造商的名称item表,或者为什么customer.address1customer.address2customer.address3字段是不是一个好主意,增加一个ORM,使其更容易为他们写数据库错误不会有任何帮助。

相反,有了正确设计的数据库和OLTP用例,ORM就是黄金。大多数繁琐的工作都可以DBIx :: Class :: Schema :: Loader之类的工具解决,我可以在几分钟之内从一个好的数据库架构转到工作的Perl代码。我会引用《帕累托规则》,说我的80%的问题已经用20%的工作解决了,但实际上,我发现的好处甚至更多。

滥用解决方案

某些人讨厌ORM的另一个原因是,他们会让抽象泄漏。让我们考虑一下MVC Web应用程序的常见情况。这是我们通常看到的(伪代码):

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

人们以这种方式编写控制器路线,并在背后轻拍自己,以为它是好代码。他们会对在控制器中对SQL进行硬编码感到震惊,但是他们所做的只是暴露了不同的SQL语法而已。他们的ORM代码需要下推到模型中,然后他们才能执行此操作:

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

你知道现在发生了什么吗?您已经正确封装了模型,没有公开ORM,后来,当您发现可以从缓存而不是数据库中获取数据时,则无需更改控制器代码(这样更容易为其编写测试并重用逻辑)。

实际上,发生的事情是人们在其控制器(和视图)上泄漏了他们的ORM代码,当他们遇到可伸缩性问题时,他们开始将责任归咎于ORM,而不是架构。ORM的说唱不好(我在很多客户中都多次看到这一点)。而是隐藏该抽象,以便在您真正达到ORM限制时,可以为您的问题选择适当的解决方案,而不是让代码与ORM紧密耦合以至于被您束之高阁。

报告和其他限制

正如Rob Kinyon在上面明确指出的那样,报告往往是ORM的弱点。这是较大问题的子集,在复杂问题中,复杂的SQL或跨越多个表的SQL有时不适用于ORM。例如,有时ORM会强制使用我不想要的联接类型,而我却不知道如何解决该问题。或者也许我想在MySQL中使用索引提示,但这并不容易。有时,SQL太复杂了,以至于编写SQL而不是提供所提供的抽象会更好。

这是我开始编写DBIx :: Class :: Report的部分原因。到目前为止,它运行良好,并且可以解决人们在此处遇到的大多数问题(只要可以使用只读界面即可)。尽管实际上看起来像拐杖,但只要您不泄漏抽象(如上一节所述),它就使工作DBIx::Class变得更加容易。

因此,何时选择DBIx :: Class?

对我来说,大多数时候我都会选择它,而我需要一个数据库接口。我已经使用多年了。但是,对于OLAP系统,我可能不会选择它,新的程序员肯定会为此而苦苦挣扎。另外,我经常发现我需要元编程,并且虽然DBIx::Class提供了工具,但它们的文档却很少。

DBIx::Class正确使用的关键与大多数ORM相同:

  1. 不要泄漏抽象。

  2. 写下你该死的测试。

  3. 知道如何根据需要使用SQL。

  4. 了解如何规范化数据库。

DBIx::Class,一旦您学会了它,它将为您处理大部分繁重的工作,并且使快速编写应用程序变得轻而易举。


1
也许您可以在不使用时添加另一个列表。:)
brian d foy 2015年

1
这是显而易见的你,但可能不是很明显许多读者(我说,在多年的#dbix-class#catalyst) -的关键,“不漏的抽象”的一点是,你在工作DBIC每一件事情是提供Cookie剪切程序行为的某些子类。强烈建议您将方法添加到子类中,并且除非您要进行Q&D工作,否则只有编写的方法才应成为公共接口的一部分。
hobbs 2015年

@hobbs:确实,这是我看到人们对此最不对劲的地方,这就是他们对DBIC的坚持。我们经常假设人们知道自己在做什么,而在大型场合却发现他们不知道。
brian d foy 2015年

9

要知道何时使用某物,了解该物的目的很重要。产品的目标是什么。

DBIx :: Class是一个ORM-对象关系映射器。ORM采用基于关系/集合的关系数据库数据结构,并将其映射到对象树。传统的映射是使用表的结构作为类描述,每行一个对象。数据库中的父子关系被视为对象之间的容器关系。

这就是它的骨头。但是,这无助于您决定是否应使用ORM。当满足以下条件时,ORM主要有用:

  • 您使用关系数据库。
  • 您的数据主要用于OLTP(在线事务处理)。
  • 您不会在应用程序内编写报告。

ORM的最大优势是构造好的SQL,以遍历覆盖在关系数据结构顶部的树形图。SQL通常是多毛的和复杂的,但这就是管理阻抗不匹配的代价。

尽管ORM非常擅长编写行提取SQL,但是却不擅长编写整理SQL。这是基于报表的SQL类型。这种排序规则是使用不同的工具而非ORM构建的。

有多种语言的ORM,Perl中有几种。其他映射器是Class :: DBI和Rose :: DB。DBIx :: Class通常在其结果集的优势上被认为比其他类更好。这是将SQL生成与SQL执行分开的概念。


更新:响应Ovid,DBIx :: Class(通过SQL :: Abstract)提供了指定要返回的列以及要使用的索引提示的功能。

但是,一般而言,如果要执行此操作,则最好不要对该特定查询使用ORM。记住-ORM的主要目的是将表中的行映射到属性为表的列的类的对象。如果仅填充某些属性,则该对象的潜在用户将不知道是否填充了哪些属性。这导致可怕的防御性编程和/或对ORM的普遍憎恨。

几乎总是希望使用索引提示或限制返回哪些列是对速度的优化和/或聚合查询。

  • 聚合查询是不是为ORM 设计的用例。尽管DBIx :: Class可以创建聚合查询,但是您并不是在创建对象图,因此直接使用DBI。
  • 使用性能优化是因为所查询的数据对于基础数据库而言太大,无论您如何访问它。大多数关系数据库非常适合具有多达1-3MM行的表的SSD,这些表中的大多数数据和索引都适合RAM。如果您的情况超出此范围,则每个关系数据库都会出现问题。

是的,出色的DBA可以使具有100MM +行的表在Oracle或SQL * Server中起作用。如果您正在阅读本文,那么您的DBA员工就不多。

综上所述,一个好的ORM不仅可以创建对象图,还可以对数据库进行自省。您可以使用它来帮助创建即席查询并像使用DBI一样使用它们,而无需创建对象图。


我认为我所看到的几乎所有内容都会写报告,这可能就是为什么人们不得不选择手动查询的原因(这就是痛苦)。
brian d foy 2015年

1
我不明白为什么您需要使用手动查询报表。我已经使用DBIC构建了一些非常复杂的报告。当然,这通常涉及大量使用“预取”和“连接”来构建大规模的定制结果集。
戴夫·克罗斯

戴夫(Dave):手动编写SQL更加容易,并且可以确保仅从三个表中提取所需的七个字段并在一行中表示它们。另外,在编写原始SQL时,提供提示更容易。
柯蒂斯·坡

>确保仅提取所需的七个字段是,这就是ResultSet搜索的“列”属性所使用的。我听到或看到的关于执行原始SQL的唯一有效参数是:1.极其复杂的子查询,通常是表/数据库设计不良的产物。2.运行DDL或其他“执行”操作,DBIC并不如此。不是真的 3.尝试破解索引提示。同样,这更多是RDBMS的弱点,但有时需要完成。并且可以将这种功能添加到DBIC中。
布伦丹·伯德

8

作为Interchange6电子商务平台(和主要模式链锯)的核心开发人员之一,我在DBIC方面具有相当深入的经验。以下是使其成为如此出色的平台的一些因素:

  • 查询生成器使您可以为多个数据库引擎编写一次(每个引擎都有多个版本)。目前,我们支持MySQL,PostgreSQL和SQLite的Interchange6,并在获得时间和资源后增加对更多引擎的支持。当前,整个项目中只有两个代码路径,它们具有其他代码来满足引擎之间的差异,这完全是由于缺少特定的数据库功能(缺少SQLite)或由于MySQL的独特性而改变了它的LEAST函数处理两个次要版本之间的NULL的方式。

  • 预定义查询意味着我可以构建可以从应用程序代码调用(带有或不带有args)的简单方法,因此我将查询大部分保留在架构定义内,而不是乱扔我的应用程序代码。

  • 可组合查询生成允许将查询分解为小的,易于理解的预定义查询,然后链接在一起以创建复杂的查询,如果这些查询是在单个步骤中构造的,则很难在任一DBIC中长期维护(甚至在纯SQL中更糟)。

  • Schema :: Loader允许我们将DBIC与旧版应用程序一起使用,从而赋予了新的生命力,并为通往未来提供了更为简单的途径。

  • DBIC插件,DeploymentHandler和Migration都极大地增加了使我的生活更简单的工具集。

DBIC与大多数其他类似ORM / ORM的平台之间的巨大差异之一是,尽管它试图引导您做事的方式,但它并不会阻止您做任何您喜欢的疯狂事情:

  • 您可以通过在查询中提供功能名称作为键来使用DBIC所不知道的SQL函数和存储过程(当您尝试在MySQL ^^中使用LEAST时也会带来一些乐趣,但这不是DBIC的错) 。

  • 当没有“ DBIC方法”来执行某项操作且返回的结果仍包装在带有访问器的漂亮类中时,可以使用文字SQL。

TL; DR对于只有几个表的非常简单的应用程序,我可能不愿意使用它,但是当我需要管理更复杂的东西时,尤其是在跨引擎兼容性和长期可维护性是关键的情况下,那么DBIC是通常是我的首选路径。


7

(在开始之前,我应该说这只是比较DBIC,DBI和基于Mojo的DBI包装器。我没有任何其他Perl ORM的经验,因此不会直接对它们进行评论)。

DBIC做得很好。我不是一个沉迷的用户,但是我知道这个理论。它在SQL生成方面做得很好,尤其是(据我所知)处理联接等。它也可以很好地预取其他相关数据。

我看到的主要优点是可以直接将结果用作模型类。这就是所谓的“添加结果集方法”,您可以在其中获取结果并针对这些结果调用方法。常见示例是从DBIC获取用户对象,然后调用一种方法来检查其密码是否有效。

当然,架构部署可能很困难,但总是很难。DBIC具有一些工具(某些在外部模块中),这些工具比手动管理自己的模式更容易,并且可能更容易。

在硬币的另一面,还有其他吸引其他感觉的工具,例如mojo风味的DBI包装纸。它们具有精简但仍可用的吸引力。大多数人还从Mojo :: Pg(原始)中获得了启发,并增加了方便的功能,例如平面文件中的模式管理和pubsub集成。

这些Mojo风格的模块是从DBIC的另一个弱点中发展出来的,DBIC的另一个弱点是(尚未)能够执行异步查询。作者向我保证,从技术上讲,甚至可能很快,但在设计合适的api时存在问题。(诚​​然,我什至被要求提供帮助,虽然我会的,但我只是不知道在我必须致力于该工作时该如何移动针头)。

TL; DR使用DBIC,除非您喜欢SQL或需要异步,在这种情况下,请研究Mojo风味的DBI包装器。


6

三年前,我在DBIC vs DBI中写了我的想法。总而言之,我列出了两个主要原因:

  1. DBIC意味着我不必考虑我编写的几乎所有应用程序所需的所有琐碎的SQL。
  2. DBIC给我的对象是数据库中的对象,而不是愚蠢的数据结构。这意味着我具有所有标准的OO优点。特别是,我发现能够向我的DBIC对象添加方法真的很有用。

至于反模式,我唯一能想到的就是性能。如果您真的想从CPU中挤出每个时钟周期,那么DBIC可能不是适合此工作的合适工具。但是,对于写的应用程序来说,这些情况越来越少了。我不记得上一次我编写了一个与数据库对话并且不使用DBIC的新应用程序。当然,如果您对调解DBIC生成的查询有所了解,那么它会有所帮助。


2
,我无法修正错字,因为我没有更改足够的字符(“ rigt ttool”)。奇怪地la脚。但这是使我感到困惑的答案。我认为在您的PerlHacks帖子中,您解决了Rob指出的一件事,但没有考虑另一件事。在许多情况下,我发现人们会回到手动SQL。
brian d foy 2015年

1

我缩放的方式:

  1. 创建一个提供DBI套接字构造函数和测试方法的类。

  2. 将该类派生到您的SQL查询类(每个sql表一个类)中,并在构造函数时测试套接字。

  3. 使用类范围的变量保存表名和主索引列名。

  4. 从这些变量中编写所有SQL插值表名称和主索引列,而不是在SQL中静态定义它们。

  5. 使用编辑器宏允许您仅输入sql语句即可进行基本的DBI方法对(准备和执行)。

如果可以做到,则可以相对轻松地整天在DBI之上编写简洁的API代码。

您会发现,很多查询将可移植到多个表中。到那时,您可以将其剪切并粘贴到EXPORTER类中,然后将它们撒回到需要的位置。这是表名称和主索引列名称的类范围内插法发挥作用的地方。

我已经使用这种方法将其扩展到数百种具有相对良好性能的DBI方法。我不想尝试以任何其他方式维护DBI代码。

何时使用DBI:始终。

我认为这不是您真正的问题。您真正的问题是:“这看起来像一个巨大的PITA,请告诉我,我不必这样做吗?”

你不知道 正确地布置它,DBI部分将变得足够冗余,从而能够使其大部分自动化。


您是否有机会可以共享一个开源项目,或者甚至只是github上的要旨以及每个类的示例?我认为您说的想法很有趣,并且可能对许多人的项目可行,但是通过一些示例进行操作会容易一些。
msouth

0

我不是Perl专家,但是我经常使用它。我不知道很多东西,或者知道如何做得更好。尽管有文档,但我还是无法理解某些内容。

我倾向于从DBI开始,因为我认为,“哦,这是一个简单的项目,我不需要ORM的膨胀,也不想麻烦设置和模式模块。” 但是很快-几乎每次-我很快就开始为这个决定诅咒自己。当我想开始在SQL查询(动态查询,而不仅仅是比较占位符)中发挥创意时,我努力使用DBI保持理智。SQL :: Abstract很有帮助,通常对我来说这足够了。但是,我下一个思想上的挣扎是在代码中维护如此多的SQL。在丑陋的heredocs中包含很多行嵌入式SQL对我来说很让人分心。也许我需要使用优质的IDE。

最后,我坚持不懈地坚持DBI。但我一直希望有更好的方法。DBIx :: Class具有一些非常整洁的特性,我已经使用了几次,但是对于最大的项目来说,它似乎太过强大了。我什至不确定要管理哪个麻烦的事情:具有分散SQL的DBI或具有分散Schema模块的DBIC。

(哦,对于DBIC来说,约束和触发器之类的东西对我来说是一个巨大的优势。)


4
这个答案不是很准确-当然,使用DBIx时会遇到问题,但是为什么会有这些问题呢?DBI是否不灵活,与DB紧密耦合,不具有可扩展性?
杰伊·埃尔斯顿,2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.