¿Deberían extraerse las consultas de la base de datos de la página misma?

10

Al escribir la generación de páginas en PHP, a menudo me encuentro escribiendo un conjunto de archivos llenos de consultas de bases de datos. Por ejemplo, podría tener una consulta para obtener algunos datos sobre una publicación directamente de la base de datos para mostrar en una página, como esta:

$statement = $db->prepare('SELECT * FROM posts WHERE id=:id');
$statement->bindValue(':id', $id, PDO::PARAM_INT);
$statement->execute();
$post = $statement->fetch(PDO::FETCH_ASSOC);
$content = $post['content']
// do something with the content

Estas consultas rápidas y únicas suelen ser pequeñas, pero a veces termino con grandes porciones de código de interacción de la base de datos que comienza a verse bastante desordenado.

En algunos casos, he resuelto este problema creando una biblioteca simple de funciones para manejar mis consultas de db relacionadas con la publicación, acortando ese bloque de código a un simple:

$content = post_get_content($id);

Y eso es genial. O al menos lo es hasta que necesite hacer algo más. Tal vez necesito obtener las cinco publicaciones más recientes para mostrar en una lista. Bueno, siempre podría agregar otra función:

$recent_posts = post_get_recent(5);
foreach ($recent_posts as $post) { ... }

Pero eso termina usando una SELECT *consulta, que generalmente no necesito de todos modos, pero a menudo es demasiado complicado para abstraerlo razonablemente. Eventualmente termino con una biblioteca masiva de funciones de interacción de base de datos para cada caso de uso, o una serie de consultas desordenadas dentro del código de cada página. E incluso una vez que he creado estas bibliotecas, me veré obligado a hacer una pequeña unión que no había usado antes, y de repente necesito escribir otra función altamente especializada para hacer el trabajo.

Claro, podría usar las funciones para casos de uso general y consultas para interacciones específicas, pero tan pronto como empiezo a escribir consultas en bruto, empiezo a volver al acceso directo para todo. O eso, o me volveré vago y comenzaré a hacer cosas en bucles PHP que de todos modos deberían hacerse directamente en las consultas MySQL.

Me gustaría preguntarles a los que tienen más experiencia en la escritura de aplicaciones de Internet: ¿vale la pena el mantenimiento de las líneas de código adicionales y las posibles ineficiencias que pueden introducir las abstracciones? ¿O simplemente el uso de cadenas de consulta directa es un método aceptable para manejar las interacciones de la base de datos?

Alexis King
fuente
Tal vez pueda usar procedimientos almacenados para "envolver" mensajes desordenados select, solo tendrá que llamar a dichos procedimientos con algunos parámetros que necesite
k102

Respuestas:

7

Cuando tiene demasiadas funciones de consulta especializadas, puede intentar dividirlas en bits componibles. Por ejemplo

$posts = posts()->joinWithComments()->orderBy("post.post_date")->first(5);

También hay una jerarquía de niveles de abstracción que puede ser útil tener en cuenta. Tienes

  1. API de mysql
  2. sus funciones mysql, como select ("select * from posts donde foo = bar"); o tal vez más componible comoselect("posts")->where("foo = bar")->first(5)
  3. funciones que son específicas de su dominio de aplicación, por ejemplo posts()->joinWithComments()
  4. funciones que son específicas de una página en particular, como commentsToBeReviewed($currentUser)

Paga mucho en términos de facilidad de mantenimiento para respetar este orden de abstracciones. Los scripts de las páginas deben usar solo funciones de nivel 4, las funciones de nivel 4 deben escribirse en términos de funciones de nivel 3, y así sucesivamente. Es cierto que esto lleva un poco más de tiempo por adelantado, pero ayudará a mantener sus costos de mantenimiento constantes con el tiempo (en lugar de "¡Dios mío, quieren otro cambio!")

xpmatteo
fuente
2
+1 Esta sintaxis esencialmente crea su propio ORM. Si realmente le preocupa que las cosas de acceso a la base de datos se vuelvan complejas, y no quiere pasar mucho tiempo jugando con los detalles, le sugiero que use un marco web maduro (por ejemplo, CodeIgniter ) que ya haya resuelto estas cosas. O al menos intente usarlo para ver qué tipo de azúcar sintáctico le da, similar a lo que ha demostrado xpmatteo.
Hartley Brody
5

La separación de las preocupaciones es un principio sobre el que vale la pena leer, vea el artículo de Wikipedia al respecto.

http://en.wikipedia.org/wiki/Separation_of_concerns

Otro principio sobre el que vale la pena leer es el acoplamiento:

http://en.wikipedia.org/wiki/Coupling_(computer_science )

Tiene dos inquietudes distintas, una es la organización de los datos de la base de datos y la segunda es la representación de esos datos. En una aplicación realmente simple, probablemente no haya mucho de qué preocuparse, ha acoplado estrechamente su capa de administración y acceso a la base de datos con su capa de representación, pero para aplicaciones pequeñas esto no es gran cosa. El problema es que las aplicaciones web tienden a evolucionar y si alguna vez desea ampliar una aplicación web, de cualquier manera, es decir, el rendimiento o la funcionalidad, entonces se encuentra con algunos problemas.

Digamos que estás generando una página web de comentarios generados por los usuarios. Luego viene el jefe de pelo puntiagudo y le pide que comience a admitir aplicaciones nativas, es decir, iPhone / Android, etc. Necesitamos un poco de salida JSON, ahora necesita extraer el código de representación que generaba HTML. Cuando hayas hecho esto, ahora tienes una biblioteca de acceso a datos con dos motores de renderizado y todo está bien, escalaste funcionalmente. Es posible que incluso haya logrado mantener todo separado, es decir, la lógica de negocios de la representación.

Luego viene el jefe y le dice que tiene un cliente que quiere mostrar las publicaciones en su sitio web, necesitan XML y necesitan alrededor de 5000 solicitudes por segundo de rendimiento máximo. Ahora necesita generar XML / JSON / HTML. Puede separar su representación nuevamente, igual que antes. Sin embargo, ahora necesita agregar 100 servidores para obtener cómodamente el rendimiento que necesita. Ahora su base de datos se ve afectada por 100 servidores con posiblemente docenas de conexiones por servidor, cada uno de los cuales está directamente expuesto a tres aplicaciones diferentes con diferentes requisitos y consultas diferentes, etc. Tener acceso a la base de datos en cada máquina frontend es un riesgo de seguridad y un crecimiento uno pero no voy a ir allí. Ahora necesita escalar para el rendimiento, cada aplicación tiene diferentes requisitos de almacenamiento en caché, es decir, diferentes preocupaciones. Puede intentar gestionar esto en una capa estrechamente acoplada, es decir, su acceso a la base de datos / lógica empresarial / capa de representación. Las preocupaciones de cada capa ahora comienzan a interferir entre sí, es decir, los requisitos de almacenamiento en caché de los datos de la base de datos podrían ser muy diferentes a los de la capa de representación, es probable que la lógica que tiene en la capa de negocios se desmorone en el SQL, es decir, moverse hacia atrás o podría desangrarse hacia la capa de representación, este es uno de los mayores problemas que he visto al tener todo en una capa, es como verter hormigón armado en su aplicación y no de una buena manera.

Hay formas estándar de abordar este tipo de problemas, es decir, el almacenamiento en caché HTTP de los servicios web (squid / yts, etc.). Caché de nivel de aplicación dentro de los propios servicios web con algo como memcached / redis. También se encontrará con problemas cuando comience a escalar su base de datos, es decir, múltiples hosts de lectura y un maestro, o datos fragmentados entre hosts. No desea que 100 hosts administren varias conexiones a su base de datos que difieren en función de las solicitudes de escritura o lectura o en una base de datos fragmentada si un usuario "usera" ingresa en "[tabla / base de datos] foo" para todas las solicitudes de escritura.

La separación de las preocupaciones es su amigo, elegir cuándo y dónde hacerlo es una decisión arquitectónica y un poco de arte. Evite el acoplamiento estrecho de todo lo que evolucionará para tener requisitos muy diferentes. Hay muchas otras razones para mantener las cosas separadas, es decir, simplifica las pruebas, la implementación de cambios, la seguridad, la reutilización, la flexibilidad, etc.

Harry
fuente
Entiendo de dónde vienes, y no estoy en desacuerdo con nada de lo que has dicho, pero esta es una pequeña preocupación en este momento. El proyecto en cuestión es personal, y la mayor parte de mi problema con mi modelo actual proviene del instinto de mi programador para evitar un acoplamiento estrecho, pero realmente soy un novato en el desarrollo complejo del lado del servidor, por lo que el final de esto fue un poco sobre mi cabeza. Aún así, +1 por lo que me parece un buen consejo, incluso si no lo sigo por completo para este proyecto.
Alexis King
Si lo que estás haciendo va a seguir siendo pequeño, lo mantendré lo más simple posible. Un buen principio a seguir aquí es YAGNI .
Harry
1

Supongo que cuando dices "la página en sí" te refieres al archivo fuente PHP que genera HTML de forma dinámica.

No consulte la base de datos y genere HTML en el mismo archivo fuente.

El archivo fuente donde consulta la base de datos no es una "página", aunque sea un archivo fuente PHP.

En el archivo fuente PHP donde crea dinámicamente el HTML, simplemente realiza llamadas a las funciones que se definen en el archivo fuente PHP donde se accede a la base de datos.

Tulains Córdova
fuente
0

El patrón que uso para la mayoría de los proyectos de escala media es el siguiente:

  • Todas las consultas SQL se separan del código del lado del servidor, en una ubicación separada.

    Trabajar con C # significa utilizar clases parciales, es decir, colocar las consultas en un archivo separado, dado que esas consultas serán accesibles desde una sola clase (ver el código a continuación).

  • Esas consultas SQL son constantes . Esto es importante, ya que evita la tentación de crear consultas SQL sobre la marcha (lo que aumenta el riesgo de inyección de SQL y al mismo tiempo hace que sea más difícil revisar las consultas más adelante).

Enfoque actual en C #

Ejemplo:

// Demo.cs
public partial class Demo : DataRepository
{
    public IEnumerable<Stuff> LoadStuff(int categoryId)
    {
        return this
            .Query(Queries.LoadStuff)
            .With(new { CategoryId = categoryId })
            .ReadRows<Stuff>();
    }

    // Other methods go here.
}

public partial class Demo
{
    private static class Queries
    {
        public const string LoadStuff = @"
select top 100 [StuffId], [SomeText]
    from [Schema].[Table]
    where [CategoryId] = @CategoryId
    order by [CreationUtcTime]";

        // Other queries go here.
    }
}

Este enfoque tiene la ventaja de tener las consultas en un archivo separado. Esto permite que un DBA revise y modifique / optimice las consultas y confirme el archivo modificado al control de origen sin entrar en conflicto con los compromisos realizados por los desarrolladores.

Un beneficio relacionado es que el control de origen puede configurarse de manera que limite el acceso de DBA a solo aquellos archivos que contienen las consultas y niegue el acceso al resto del código.

¿Es posible hacerlo en PHP?

PHP carece de clases parciales y clases internas, por lo que no se puede implementar en PHP.

Puede crear un archivo separado con una clase estática separada ( DemoQueries) que contenga las constantes, dado que la clase será accesible desde cualquier lugar. Además, para evitar contaminar el alcance global, puede colocar todas las clases de consulta en un espacio de nombres dedicado. Esto creará una sintaxis bastante detallada, pero dudo que pueda evitar la verbosidad:

namespace Data {
    public class Demo inherit DataRepository {
        public function LoadStuff($categoryId) {
            $query = \Queries\Demo::$LoadStuff;
            // Do the stuff with the query.
        }

        // Other methods go here.
    }
}

namespace Queries {
    public static class Demo {
        public const $LoadStuff = '...';

        // Other queries go here.
    }
}
Arseni Mourzenko
fuente