¿Cuándo debo usar DBIx :: Class de Perl?

17

DBIx :: Class es una interfaz Perl popular para cualquier base de datos a la que pueda conectarse a través de DBI . Hay buena documentación para sus detalles técnicos, pero poca información sobre su uso adecuado (incluidas las situaciones en las que probablemente no la desee).

En muchas situaciones, las personas lo buscan reflexivamente porque piensan que deberían usarlo para todo lo que involucra una base de datos. Sin embargo, con mayor frecuencia lo vi mal utilizado hasta el punto en que se convierte en un punto de dolor. Mi pregunta durante las revisiones de código y arquitectura es siempre "¿Qué beneficio le está dando Foo?" Muy a menudo, los desarrolladores que veo en estas situaciones no pueden formar una respuesta coherente a eso. Pero entonces, a menudo tampoco entienden el SQL simple.

Durante los últimos meses he estado preguntando a la gente "¿Por qué usas DBIx :: Class?" y solo recibí una buena respuesta (y esa persona también pudo responder el seguimiento "¿Cuándo no la usarías?"). Peter Rabbitson, el desarrollador principal, se acercó a una respuesta en su entrevista en FLOSS Weekly , pero está un poco enterrado en el medio de la entrevista.

Entonces, ¿cómo puedo decidir si usar DBIx :: Class es apropiado para un proyecto?

brian d foy
fuente
3
¿Estás escribiendo otro libro? :)
simbabque
1
En mi experiencia, el dolor surge cuando se usa DBIC en situaciones en las que es excesivo. Y, a pesar de ser muy poderoso, a menudo es excesivo porque las personas solo usan las características básicas (generación de SQL y combinaciones) y no necesitan nada más. Es por eso que escribí DBIx :: Lite, que proporciona esas características básicas y no requiere ningún esquema para codificar.
Alessandro

Respuestas:

24

Antes de responder a la pregunta, creo que algunos antecedentes están en orden.

El núcleo del problema

Después de años de entrevistar y contratar desarrolladores, aprendí dos cosas:

  1. La gran mayoría de los desarrolladores tienen muy poca experiencia en el diseño de bases de datos.

  2. He notado una correlación floja entre los que no entienden las bases de datos y los que odian los ORM.

(Nota: y sí, sé que hay quienes entienden muy bien las bases de datos que odian los ORM)

Cuando la gente no entiende por qué claves externas son importantes, ¿por qué no incrusta el nombre del fabricante en la itemmesa, o por qué customer.address1, customer.address2y customer.address3los campos no son una buena idea, la adición de un ORM para que sea más fácil para ellos errores de base de datos de escritura No va a ayudar nada.

En cambio, con una base de datos diseñada adecuadamente y un caso de uso OLTP, los ORM son dorados. La mayor parte del trabajo duro desaparece y con herramientas como DBIx :: Class :: Schema :: Loader , puedo pasar de un buen esquema de base de datos a un código Perl en cuestión de minutos. Citaría la Regla de Pareto y diría que el 80% de mis problemas se han resuelto con el 20% del trabajo, pero en realidad, encuentro los beneficios aún mayores que eso.

Abusando de la solución

Otra razón por la que algunas personas odian los ORM es porque dejarán escapar la abstracción. Consideremos el caso común de las aplicaciones web MVC. Aquí hay algo que comúnmente vemos (pseudocó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,
   });
}

La gente escribe rutas de control así y se da palmaditas en la espalda, pensando que es un código bueno y limpio. Estarían horrorizados al codificar el SQL en sus controladores, pero han hecho poco más que exponer una sintaxis SQL diferente. Su código ORM debe insertarse en un modelo y luego pueden hacer esto:

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

¿Sabes lo que pasó ahora? Ha encapsulado correctamente su modelo, no ha expuesto el ORM, y más tarde, cuando descubra que puede obtener esos datos de un caché en lugar de la base de datos, no necesita cambiar el código de su controlador (y es más fácil escribir pruebas para ello y reutilizar la lógica).

En realidad, lo que sucede es que las personas filtran su código ORM en todos sus controladores (y vistas) y cuando se topan con problemas de escalabilidad, comienzan a culpar al ORM en lugar de a su arquitectura. El ORM tiene una mala reputación (lo veo repetidamente para muchos clientes). En su lugar, oculte esa abstracción para que cuando haya alcanzado realmente los límites de ORM, pueda elegir las soluciones apropiadas para su problema en lugar de permitir que el código esté tan estrechamente unido al ORM que esté atado.

Informes y otras limitaciones

Como Rob Kinyon dejó en claro anteriormente, los informes tienden a ser una debilidad en los ORM. Este es un subconjunto de un problema mayor en el que SQL complicado o SQL que abarca varias tablas a veces no funciona bien con ORM. Por ejemplo, a veces el ORM fuerza un tipo de unión que no quiero y no puedo decir cómo solucionarlo. O tal vez quiero usar una pista de índice en MySQL, pero no es fácil . O, a veces, el SQL es tan complicado que sería mejor escribir el SQL en lugar de la abstracción proporcionada.

Esta es parte de la razón por la que comencé a escribir DBIx :: Class :: Report . Hasta ahora funciona bien y resuelve la mayoría de los problemas que las personas tienen aquí (siempre y cuando estén bien con una interfaz de solo lectura). Y aunque parezca una muleta, en realidad, siempre y cuando no esté filtrando su abstracción (como se explicó en la sección anterior), hace que trabajar con él sea DBIx::Classaún más fácil.

Entonces, ¿cuándo elegiría DBIx :: Class?

Para mí, lo elegiría la mayoría de las veces que necesito una interfaz para una base de datos. Lo he estado usando por años. Sin embargo, es posible que no lo elija para un sistema OLAP, y los programadores más nuevos ciertamente tendrán dificultades con él. Además, a menudo encuentro que necesito meta-programación y, aunque DBIx::Classproporciona las herramientas, están muy poco documentadas.

La clave para usar DBIx::Classcorrectamente es la misma que para la mayoría de los ORM:

  1. No pierdas la abstracción.

  2. Escribe tus malditas pruebas.

  3. Sepa cómo desplegarse a SQL, según sea necesario.

  4. Aprenda a normalizar una base de datos.

DBIx::Class, una vez que lo aprenda, se encargará de la mayor parte de su trabajo pesado y hará que sea muy fácil escribir aplicaciones rápidamente.

Curtis Poe
fuente
1
Tal vez pueda agregar otra lista para cuando no la usaría. :)
brian d foy
1
Esto es obvio para usted, pero probablemente no sea obvio para muchos lectores (digo, después de haber pasado años #dbix-classy #catalyst): la clave para el bit de "no filtrar la abstracción" es que cada cosa con la que está trabajando en DBIC es Una subclase de algo que proporciona el comportamiento de cortador de galletas. Le recomendamos encarecidamente que agregue métodos a sus subclases y, a menos que haga un trabajo de preguntas y respuestas, solo los métodos que escribió deben formar parte de su interfaz pública.
hobbs
@hobbs: De hecho, ahí es donde veo que la gente se equivoca más con esto y es cómo se quedan atrapados con DBIC. A menudo asumimos que las personas saben lo que están haciendo en lo pequeño, pero descubrimos en lo grande que no.
brian d foy
9

Para saber cuándo usar algo, es importante comprender cuál es el propósito de la cosa. Cuál es el objetivo del producto.

DBIx :: Class es un ORM - Mapper Relacional de Objetos. Un ORM toma las estructuras de datos de bases de datos relacionales basadas en conjuntos / relacionales y las asigna a un árbol de objetos. El mapeo tradicional es un objeto por fila, usando la estructura de la tabla como una descripción de clase. Las relaciones padre-hijo en la base de datos se tratan como relaciones de contenedor entre objetos.

Eso es lo esencial. Pero eso no le ayuda a decidir si debe usar un ORM. Los ORM son principalmente útiles cuando se cumple lo siguiente:

  • Utiliza una base de datos relacional.
  • Sus datos se utilizan en gran medida para OLTP (procesamiento de transacciones en línea).
  • No escribe informes dentro de su aplicación.

La mayor fortaleza que tiene un ORM es construir un buen SQL para recorrer un gráfico de árbol superpuesto sobre la estructura de datos relacionales. El SQL a menudo es complicado y complejo, pero ese es el precio de administrar la falta de coincidencia de impedancia.

Si bien los ORM son muy buenos para escribir SQL de extracción de filas, son muy pobres para escribir SQL intercalado. Este es el tipo de SQL en el que se basan los informes. Este tipo de intercalación se crea utilizando diferentes herramientas, no un ORM.

Hay muchos ORM en varios idiomas, varios en Perl. Otros mapeadores son Class :: DBI y Rose :: DB. DBIx :: Class a menudo se considera mejor que los demás en gran medida por la solidez de sus conjuntos de resultados. Este es un concepto donde la generación de SQL está separada de la ejecución de SQL.


Actualización : en respuesta a Ovid, DBIx :: Class (a través de SQL :: Abstract) ofrece la capacidad de especificar qué columnas devolver y qué sugerencias de índice utilizar.

Sin embargo, en general, si desea hacer esto, es mejor que no use un ORM para esa consulta específica. Recuerde: el propósito principal de un ORM es asignar filas en una tabla a objetos de una clase cuyos atributos son las columnas de la tabla. Si solo está completando algunos de los atributos, los usuarios potenciales de ese objeto no sabrán qué atributos se rellenan o no. Esto lleva a una horrible programación defensiva y / o un odio general hacia los ORM.

Casi siempre, el deseo de usar sugerencias de índice o limitar qué columnas se devuelven es una optimización para la velocidad y / o una consulta agregada.

  • Las consultas de agregación son el caso de uso para el que los ORM NO están diseñados. Si bien DBIx :: Class puede crear consultas de agregación, no está creando un gráfico de objeto, por lo que solo debe usar DBI directamente.
  • Las optimizaciones de rendimiento se utilizan porque los datos que se consultan son demasiado grandes para la base de datos subyacente, independientemente de cómo acceda a ellos. La mayoría de las bases de datos relacionales son ideales para tablas con filas de hasta 1-3MM que se ejecutan fuera de SSD donde la mayoría de los datos + índices encajan en la RAM. Si su situación es mayor que esta, entonces cada base de datos relacional tendrá problemas.

Sí, un gran DBA puede hacer que las tablas con filas de 100MM + funcionen en Oracle o SQL * Server. Si está leyendo esto, no tiene un excelente DBA en el personal.

Dicho todo esto, un buen ORM hace más que solo crear gráficos de objetos: también proporciona una definición introspectiva de su base de datos. Puede usar esto para ayudar a crear consultas ad-hoc y consumirlas como lo haría con DBI, sin crear el gráfico de objetos.

Rob Kinyon
fuente
Creo que casi todo lo que veo escribe informes, lo que probablemente sea la razón por la cual las personas tienen que ir a consultas manuales (y ese es el dolor).
brian d foy
1
No entiendo por qué necesita desplegarse a consultas manuales para informes. He creado algunos informes bastante complejos con DBIC. Por supuesto, a menudo implica la creación de un conjunto de resultados personalizado masivo con un uso intensivo de 'captación previa' y 'unión'.
Dave Cross
Dave: el SQL manual puede ser mucho más fácil de escribir y garantizar que solo extraiga los siete campos que necesita de tres tablas y los represente en una sola fila. Además, es mucho más fácil proporcionar sugerencias al escribir SQL sin formato.
Curtis Poe
> para asegurarse de que solo está utilizando los siete campos que necesita Sí, para eso se usan las "columnas" para las búsquedas de ResultSet. Los únicos argumentos válidos que he escuchado o visto para hacer SQL sin formato son: 1. Subconsultas enormemente complejas, que generalmente son el producto de una tabla / DB mal diseñada 2. Ejecutar DDL u otras operaciones 'do', que DBIC no es Realmente construido para. 3. Intentando piratear las pistas de índice. Una vez más, eso es más una debilidad del RDBMS, pero a veces hay que hacerlo. Y es posible agregar ese tipo de funcionalidad a DBIC.
Brendan Byrd
8

Como uno de los principales desarrolladores de la plataforma de comercio electrónico Interchange6 (y la motosierra de esquema principal) tengo una experiencia bastante profunda con DBIC. Estas son algunas de las cosas que la hacen una plataforma tan genial:

  • El generador de consultas le permite escribir una vez para muchos motores de bases de datos (y múltiples versiones de cada uno). Actualmente admitimos Interchange6 con MySQL, PostgreSQL y SQLite y agregaremos soporte para más motores una vez que tengamos el tiempo y los recursos. Actualmente, solo hay dos rutas de código en todo el proyecto que tienen un código adicional para satisfacer las diferencias entre los motores y esto se debe simplemente a la falta de una función de base de datos específica (falta SQLite en algunos lugares) o debido a la idiotez de MySQL que cambió la forma en que su función MENOS maneja NULL entre dos versiones menores.

  • Las consultas predefinidas significan que puedo crear métodos simples a los que se puede llamar (con o sin argumentos) desde el código de la aplicación, por lo que mantengo mis consultas principalmente dentro de la definición del esquema en lugar de ensuciar el código de mi aplicación.

  • La generación de consultas componibles permite dividir las consultas en pequeñas consultas predefinidas y entendibles y luego encadenarlas para crear consultas complejas que serían difíciles de mantener a largo plazo en DBIC (incluso peor en SQL puro) si se construyeran en un solo paso.

  • Schema :: Loader nos ha permitido usar DBIC con aplicaciones heredadas que nos dan una nueva oportunidad de vida y un camino mucho más simple hacia el futuro.

  • Los complementos DBIC, DeploymentHandler y Migration se agregan inmensamente al conjunto de herramientas que simplifican mi vida.

Una de las grandes diferencias entre DBIC y la mayoría de las otras plataformas similares a ORM / ORM es que, aunque trata de guiarte en su forma de hacer las cosas, tampoco te impide hacer locuras que te gustan:

  • Puede usar funciones SQL y procedimientos almacenados que DBIC no conoce simplemente al proporcionar el nombre de la función como clave en la consulta (también puede ser divertido cuando intenta usar LEAST en MySQL ^^ pero eso no es culpa de DBIC) .

  • El SQL literal se puede usar cuando no hay una 'forma DBIC' para hacer algo y el resultado devuelto todavía está envuelto en clases agradables con accesores.

TL; DR Probablemente no me molestaría en usarlo para aplicaciones realmente simples con solo un par de tablas, pero cuando necesito administrar algo más complejo, especialmente donde la compatibilidad entre motores y la mantenibilidad a largo plazo son clave, entonces DBIC es Generalmente mi camino preferido.

SysPete
fuente
7

(Antes de comenzar, debería decir que esto solo compara los envoltorios DBI, DBI y DBI basados ​​en Mojo. No tengo experiencia con ningún otro ORM de Perl, por lo que no comentaré sobre ellos directamente).

DBIC hace muchas cosas muy bien. No lo uso mucho, pero conozco la teoría. Hace un trabajo bastante bueno en la generación de SQL y especialmente (como me han dicho) manejando uniones, etc. También puede hacer una captación previa de otros datos relacionados.

La principal ventaja que veo es la capacidad de usar DIRECTAMENTE los resultados como su clase de modelo. Esto también se conoce como "agregar métodos de conjunto de resultados" en el que puede obtener sus resultados y llamar a métodos sobre esos resultados. El ejemplo común es buscar un objeto de usuario de DBIC y luego llamar a un método para verificar si su contraseña es válida.

La implementación del esquema seguro puede ser difícil, pero siempre es difícil. DBIC tiene herramientas (algunas en módulos externos) que lo hacen más fácil, y probablemente más fácil que administrar sus propios esquemas a mano.

En el otro lado de la moneda, hay otras herramientas que atraen a otras sensibilidades, como los envoltorios DBI con sabor a mojo. Estos tienen el atractivo de ser delgados y aún así utilizables. La mayoría también ha seguido el ejemplo de Mojo :: Pg (el original) y ha agregado funciones útiles como la administración de esquemas en archivos planos y la integración de pubsub.

Estos módulos con sabor a Mojo surgieron de otro punto débil de DBIC, que es que (todavía) no es capaz de hacer consultas asincrónicas. Los autores me han asegurado que es técnicamente posible tal vez incluso rápidamente, pero hay problemas para diseñar una API que sea adecuada. (Es cierto que incluso me han pedido que ayude con esto, y aunque lo haría, simplemente no sé cómo mover la aguja en el tiempo que tengo que dedicarle).

TL; DR usa DBIC a menos que ames SQL o necesites asíncrono, en cuyo caso investiga los envoltorios DBI con sabor a Mojo.

Joel Berger
fuente
6

Escribí mis pensamientos sobre esto en DBIC vs DBI hace tres años. Para resumir, enumeré dos razones principales:

  1. DBIC significa que no tengo que pensar en todo el SQL trivial que se necesita para prácticamente cualquier aplicación que escriba.
  2. DBIC me devuelve objetos de la base de datos en lugar de estructuras de datos tontas. Esto significa que tengo toda la bondad estándar de OO para jugar. En particular, me resulta muy útil poder agregar métodos a mis objetos DBIC.

En cuanto a los antipatrones, bueno, lo único en lo que puedo pensar es en el rendimiento. Si realmente desea exprimir cada ciclo de reloj de su CPU, entonces tal vez DBIC no sea la herramienta adecuada para el trabajo. Pero, ciertamente para las aplicaciones que escriben, esos casos son cada vez más raros. No recuerdo la última vez que escribí una nueva aplicación que habló con una base de datos y no usó DBIC. Por supuesto, ayuda si sabe un poco sobre cómo ajustar las consultas que genera DBIC.

Dave Cross
fuente
2
Eh, no puedo corregir los errores tipográficos porque no cambio suficientes caracteres ("derecho a la herramienta"). Curiosamente cojo. Pero este es el tipo de respuesta que me desconcierta. Creo que en tu publicación de PerlHacks, abordas una cosa que Rob señala, pero no consideras la otra. En muchos casos, he encontrado personas que vuelven al manual SQL.
brian d foy
1

La forma en que lo hago escala:

  1. cree una clase que proporcione el constructor de socket DBI y los métodos de prueba.

  2. deriva esa clase en tus clases de consulta SQL (una clase por tabla sql) y prueba el socket en el momento del constructor.

  3. use variables de ámbito de clase para mantener el nombre de la tabla y los nombres de las columnas del índice primario.

  4. Escriba todo el nombre de la tabla de interpolación de SQL y la columna de índice primario de esas variables en lugar de definirlas en el SQL estáticamente.

  5. use macros de editor para permitirle hacer pares de métodos DBI básicos (preparar y ejecutar) mientras escribe SOLO la instrucción sql.

Si puede hacer eso, puede escribir código API limpio sobre el DBI todo el día con relativa facilidad.

Lo que encontrará es que muchas de sus consultas serán portables en varias tablas. En ese punto, puede cortar y pegar en una clase EXPORTADOR y espolvorearlos nuevamente donde sea necesario. Aquí es donde entra en juego la interpolación de clase del nombre de la tabla y los nombres de las columnas del índice primario.

He utilizado este enfoque para escalar a cientos de métodos DBI con un rendimiento relativamente bueno. No quisiera intentar mantener el código DBI de otra manera.

Cuándo usar el DBI: siempre.

Sin embargo, no creo que esa sea tu verdadera pregunta. Su verdadera pregunta fue: "Esto parece una gran PITA, ¿por favor dime que no tengo que hacer esto?"

Usted no Extiéndalo bien y la parte DBI se vuelve lo suficientemente redundante como para poder automatizarla principalmente.

James Aanderson
fuente
¿Hay alguna posibilidad de que tenga un proyecto de código abierto que pueda compartir, o tal vez incluso una idea general de Github con un ejemplo de cada clase? Creo que las ideas que está diciendo son interesantes y probablemente serían viables para los proyectos de muchas personas, pero sería un poco más fácil comenzar con algunos ejemplos.
msouth
0

No soy un experto en Perl, pero lo uso mucho. Hay muchas cosas que no sé o sé cómo hacer mejor; algunas cosas que todavía no soy capaz de entender, a pesar de la documentación.

Tiendo a comenzar con DBI porque pienso: "Oh, este es un proyecto simple, no necesito la hinchazón de un ORM y no quiero molestarme con la configuración y los módulos de esquema". Pero muy rápidamente, casi siempre, empiezo a maldecirme por esa decisión. Cuando quiero comenzar a ser creativo en mis consultas SQL (consultas dinámicas, no solo marcadores de posición de comparación) me esfuerzo por mantener la cordura usando DBI. SQL :: Abstract ayuda mucho, y normalmente eso es suficiente para mí. Pero mi próxima lucha mental es mantener tanto SQL dentro de mi código. Me distrae mucho tener líneas y líneas de SQL incrustado dentro de feos heredocs. Tal vez necesito usar un IDE de calidad.

Al final, la mayoría de las veces, me quedo con DBI directo. Pero sigo deseando que hubiera una mejor manera. DBIx :: Class tiene algunas características realmente buenas y lo he usado varias veces, pero parece demasiado exagerado para todos, excepto para los proyectos más grandes. Ni siquiera estoy seguro de qué me resulta más engorroso administrar: DBI con SQL disperso o DBIC con módulos de esquema dispersos.

(Oh, cosas como restricciones y disparadores son una gran ventaja para DBIC).

Stefan Adams
fuente
44
Esta respuesta no llega al punto muy bien: claro, tiene problemas cuando usa DBIx, pero ¿por qué tiene esos problemas? ¿DBI no es flexible, está demasiado unido al DB, no es escalable, o qué?
Jay Elston