2009-07-22 23 views
25

Cuando programo en PHP siempre trato de crear 'modelos' significativos (clases) que corresponden a tablas en la base de datos. A menudo encuentro el siguiente problema:Limpiar estructura OO vs. rendimiento de SQL

Suponiendo que he creado una base de datos con dos tablas: authors y blogs, que tienen un modelo correspondiente en mi aplicación.

Digamos que quiero imprimir todos los blogs, junto con información sobre el autor, que tendría que hacer algo como esto:

<?php 
foreach ($app->getBlogs() as $blog) { 
    echo "<h1>" . $blog->title . "</h1>"; 
    echo "Written by" . $blog->getAuthor()->name . "</p>"; 
    // ... et cetera 
} 
?> 

El problema es que la aplicación será ahora el fuego 1 consulta SQL a obtenga todos los artículos del blog y las consultas de [número de artículos del blog] para obtener la información de cada autor. Tener SQL directo utilizado Podría haber recuperado esta información mediante una consulta sencilla:

SELECT * FROM blogs 
JOIN authors ON authors.id = blogs.author 

¿Cuál es la mejor manera de hacer frente a estas cuestiones: el desarrollo de una aplicación orientada a objetos sin ejecutar demasiadas consultas SQL inútiles.

Respuesta

3

IMO, creo que deberías simplemente escribir otra clase que encapsule lo que tienes. ¿Siempre tiene sentido que un blog tenga un autor? ¿Todos los autores tienen un blog? ¿Puede un autor tener múltiples blogs? Piense en estos problemas, luego diseñe una clase que lo encapsule. Recuerde, los esquemas de base de datos típicos no son OO ... son relacionales. Sí, están cerca, pero hay diferencias sutiles.

Así que si un autor puede tener varios blogs, puede tener una clave de clase de varios valores (con la clave basada en el ID de autor) y puede inicializar o cargar esta clase con una llamada SQL. Solo algunas cosas en que pensar.

1

Cualquier solución que use, ORM o no, debería ser capaz si se emite una única selección en este caso, y también debería ser capaz de seleccionar solo las columnas necesarias. Luego, a partir de esa unión, debe ser capaz de poblar los objetos de los autores con las listas correspondientes de blogs por autor. Tener que emitir múltiples SQL es un desperdicio.

0

Este es el problema clásico de ORM. Muchas muchas escuelas de pensamiento. No estoy seguro de las especificaciones de php, pero existen varias estrategias para resolver este "desajuste de impedancias". Google orm.

2

Este tipo de cosas es exactamente lo que la creación de su propia capa de datos debería resolver para usted. En su modelo para sus blogs, debe haber una función como getBlogList() que devolverá los títulos del blog y el nombre del autor todo en una consulta.

+0

Algún tipo de solución en la que he pensado, pero el punto es: no sé de antemano si alguna vez se necesita el objeto de autor, $ blog-> getAuthor() podría no llamarse nunca pero lo habría recuperado la información, no obstante, con la solución provista. – Thijs

+0

Lo que estás preguntando es imposible, entonces. Básicamente estás pidiendo un código que de alguna manera sepa de antemano qué vas a usar. Tienes que codificarlo tú mismo; debe elegir la carga diferida, que es eficiente si solo quiere unos pocos Autores pero no todos, o la carga por lotes, como se describió anteriormente, que es eficiente si necesita muchos Autores. No puedes tener ambos. – ryeguy

+0

La herramienta ORM que usamos aquí en Inntec habría generado clases para (a) derribar un autor único y otro para (b) retirar una colección de autores. Por lo tanto, podría decir fácilmente "obtener todos los autores que tengan la letra 'm' en el primer nombre" o casi cualquier otra cosa. Sin embargo, ambos (a) y (b) despliegan todo el autor, por lo que todos los campos van por el cable. Si quisiera un solo campo para un caso especial, o si necesitara una unión inusual, tendría que escribirlo usted mismo. Lo cual está bien. Utilizo el código gen'd el 90% del tiempo. –

0

Una forma de hacer esto es crear una vista con su combinación en ella y ver los resultados de la vista de mapa a otra clase que contenga datos para blog y autor.

1

Propel es un ejemplo de PHP ORM que puede hacer frente a esto. Estoy seguro de que Doctrine debe poder hacerlo, aunque nunca lo he visto.

¿Por qué reinventar la rueda?

3

A menos que sepa con certeza que las operaciones sql ineficientes no tendrán un impacto real, como el número de iteraciones redundantes o las filas afectadas siempre será pequeño (por ejemplo, iterar una operación sobre el número de hijos de una familia, que, a excepción de casos muy raros como los Duggars, se puede confiar en que es menos de 10), siempre he favorecido la eficiencia de la consulta relacional sobre la belleza del código OO.

Aunque el código OO feo puede hacer que el mantenimiento sea un problema, el acceso a datos ineficientes puede hacer que un sistema se ponga de rodillas, generalmente cuando está de vacaciones o tratando de dormir. Y la mayoría de las veces, puede encontrar un buen compromiso que haga que las operaciones SQL más eficientes tengan una interfaz razonablemente "objetiva". Puede costarle un poco más de tiempo cuando se trata de refactorizar o agregar funciones si su modelo de objetos no es hermoso, pero le está costando a sus clientes tiempo cada vez que presionan ese botón (o dinero en términos de hardware más grande para ejecutar el aplicación - nunca es un buen método de optimización), y las horas hombre dedicadas a usar la aplicación deberían sobrepasar con creces las horas hombre invertidas en su desarrollo (uno esperaría).

En cuanto a sus preocupaciones sobre si se necesitará una interfaz (que le obligue a descubrir todos los patrones de consumo posibles), he solucionado todos mis cambios de datos mediante procedimientos almacenados, pero permitiendo que el acceso a los datos sea correcto contra las tablas & vistas dando a todos los usuarios privilegios de selección solamente. Esta es una posición polémica, ya que a muchas personas les gustaría bloquear todas las operaciones de acceso a los datos de los consumidores intermedios a fin de garantizar que todos los sql que se ejecutan se ajusten a sus estándares. Pero siempre aparecen nuevas formas de ver los datos, y si tiene que agregar un nuevo proceso almacenado, actualizar sus bibliotecas de clases principales y actualizar su código de cliente cada vez que alguien quiera implementar una nueva característica, la implementación y la calificación pueden crecer hasta ser una carga real, mucho más que tener que lidiar con un modelo de objetos que no se ajusta a un ideal religioso. Y es mucho más fácil implementar un proceso de inspección de código que verifica que las nuevas declaraciones seleccionadas escritas por consumidores intermedios sean kosher.

0

Honestamente, basta con crear un método en su clase blog llamado getBlogsWithAuthors() y luego ejecutar

SELECT * 
FROM blogs 
JOIN authors 
     ON authors.id = blogs.author 

Sé que puede parecer como un dolor de escribir cosas como esta para cada clase del modelo, pero no es realmente ninguna otra manera. Se podría hacer un poco más dinámica, sin embargo:

//this is a method of a model class. 
//Assume $this->table is the table name of the model (ie, Blog) 
public function getWith($joinTable, $pivot1, $pivot2) 
{ 
    $sql="SELECT * 
      FROM {$this->table} 
      JOIN $joinTable 
        ON $pivot1 = $pivot2"; 

    return executeQuery($sql);  
} 

$blog=new Blog(); 
$result=$blog->getWith('authors', 'authors.id', 'blogs.author'); 
[play with results here] 
+0

Para ser sincero, esta solución me parece un poco hacky. El método getWith() no es muy descriptivo y no concuerda con la encapsulación defendida por el diseño orientado a objetos porque otros objetos deben conocer el funcionamiento interno de la clase Blog (nombres de columnas, candidatos a unirse). – Thijs

+0

Bueno, la idea era que esto podría estar en su clase de modelo base y podría adaptarse sin ninguna codificación rígida a ningún modelo. Si quieres algo más elegante, ¿por qué lo estás codificando manualmente? Ir por propel o doctrina. Simplemente estás reinventando la rueda de otra manera. – ryeguy

+0

Creo que RyeGuy tiene razón en este caso. ;) SI su solución es hackish, es porque es demasiado minimalista. No me gusta pasar elementos de la base de datos como texto. Pero, ¿cuál es el siguiente paso? Haciendo una clase separada para cada entidad con más herramientas (enumeraciones, etc.) para las columnas.Pero eso lleva mucho tiempo y es frágil (piense en los cambios de la base de datos, etc.), entonces, ¿qué sigue? La generación de código resuelve una tonelada. Y si hay generación de código, entonces podríamos hacer mucho más. Y así nació mi herramienta ORM. Literalmente comenzó en ASP Classic, utilizando MS Access para generar clases. :) –

3

Soy un gran defensor de ORM, y aquí está mi pesaje:

Está bien para el comercio una cantidad inperceptible de rendimiento de las aplicaciones de una tonelada del rendimiento del desarrollador. Los servidores son extremadamente poderosos en la actualidad y ese hierro extra nos brinda una nueva flexibilidad.

Dicho esto, si haces algo tonto que borra la experiencia del usuario poniendo al servidor de rodillas, ya no está bien. Si tuvieras un millón de autores en tu ejemplo, tirarlos a todos junto con todos sus campos e iterarlos sería imprudente. Si solo tienes 20 autores, entonces no es gran cosa.

En el caso de grandes conjuntos de datos y costosas operaciones por lotes, incluso como chico ORM, tengo que optimizar y escribir sprocs especiales o declaraciones SQL solo para ese caso. Y tengo que tener cuidado de no escribir mi código de tal manera que martilleé la base de datos, ¿sería mejor usar un patrón de caché en el que despliegue un gran conjunto de datos y luego trabaje en eso?

Este es un gran debate continuo, pero para mí es solo cuestión de entender que no puede resolver todos los problemas con una sola herramienta.

+0

Gracias por su comentario. ¿Alguna vez habrá una compensación entre el rendimiento y el diseño limpio? ¿No hay una forma de escribir código reutilizable y limpio orientado a objetos y ejecutar consultas eficientes seleccionando los datos correctos? – Thijs

+0

thijs: Creo que puedes tener tu pastel y comértelo también. Si tiene que escribir una declaración sql única, simplemente haga lo posible para encapsular ese código de acceso a datos en una clase en algún lugar utilizando un patrón que sea coherente en toda la aplicación. No tiene que ser horrible. ;) Tengo proyectos en los que hay una clase llamada 'sprocs' que contiene código para acceder a todos mis procedimientos almacenados. Entonces puedo hacer algo como: myData = Sprocs.AuthorNames, y es muy consistente. Muchas herramientas ORM autogeneran ese tipo de cosas para usted, lo cual es bueno. –

+0

¿Cómo sabes que tienes 20 autores cuando los usuarios pueden agregar más en cualquier momento? ¿Pones restricciones CHECK en el número de filas en la tabla o qué? Este tipo de planificación de capacidad es un desastre que está por ocurrir. – wqw

1

que utilizan algunos otros enlaces .. Ejemplo:

<?php 
$blogs = $app->getBlogs(); 
$blogs->getAuthor(); 
foreach ($blogs as $blog) { 
    echo "<h1>" . $blog->title . "</h1>"; 
    echo "Written by" . $blog->getAuthor()->name . "</p>"; 
    // ... et cetera 
} 
?> 

-> getAutor() llamada en el blog $ consulta la base de datos sólo una vez, y de acuerdo al objeto especial de matriz, la llamada getAutor() se llama en cada uno (pero, de alguna manera, está optimizado para ejecutarse solo como una consulta).

0

Siempre puede usar memcached como capa intermedia. Cada consulta sería puramente basada en RAM, lo que significa que puede ejecutar tantas como desee.

1

Usted ya ha contestado a la pregunta:

Después de haber utilizado SQL sencilla que podría haber recuperado esta información mediante una simple consulta

Puede elegir entre SQL que obtiene solamente el blog publicaciones y SQL que recupera entradas de blog y autores. Del mismo modo, puede elegir entre un código PHP que obtiene solo entradas de blog o código PHP que recupera entradas de blog y autores. Debe elegir su código PHP, al igual que tiene que elegir su SQL.

Existen muchos ejemplos arriba que demuestran cómo esto funcionaría en la práctica. La recomendación de usar Doctrine o Propel también es buena.

1

Considere la separación de comando/consulta según lo descrito por Greg Young y Martin Fowler. Su modelo de consulta puede tener Blog y Autor des-normalizados en una sola tabla optimizada para recuperar DTO para su capa de presentación.

Greg Young tiene una excelente presentación en CQS en InfoQ.