Le query del database devono essere astratte dalla pagina stessa?


10

Quando scrivo la generazione di pagine in PHP, mi ritrovo spesso a scrivere una serie di file disseminati di query sul database. Ad esempio, potrei avere una query per recuperare alcuni dati su un post direttamente dal database per visualizzarli su una pagina, in questo modo:

$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

Queste query rapide e una tantum sono in genere piccole, ma a volte finisco con grandi porzioni di codice di interazione del database che inizia a sembrare piuttosto disordinato.

In alcuni casi, ho risolto questo problema creando una semplice libreria di funzioni per gestire le mie query db post-correlate, abbreviando quel blocco di codice in un semplice:

$content = post_get_content($id);

Ed è fantastico. O almeno lo è fino a quando devo fare qualcos'altro. Forse ho bisogno di ottenere i cinque post più recenti da visualizzare in un elenco. Bene, potrei sempre aggiungere un'altra funzione:

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

Ma questo finisce per usare una SELECT *query, che di solito in realtà non mi serve, ma spesso è troppo complicata per essere ragionevolmente astratta. Alla fine finisco con una vasta libreria di funzioni di interazione del database per ogni singolo caso d'uso, o una serie di domande disordinate all'interno del codice di ogni pagina. E anche una volta che avrò creato queste librerie, mi ritroverò a dover fare un piccolo join che non avevo mai usato prima e improvvisamente ho bisogno di scrivere un'altra funzione altamente specializzata per fare il lavoro.

Certo, potrei usare le funzioni per casi di uso generale e query per interazioni specifiche, ma non appena inizio a scrivere query non elaborate inizio a tornare all'accesso diretto per tutto. O quello, o diventerò pigro e inizierò a fare cose nei loop di PHP che dovrebbero davvero essere fatte direttamente nelle query MySQL, comunque.

Vorrei chiedere a coloro che hanno più esperienza con la scrittura di applicazioni Internet: l'incremento della manutenibilità vale le righe extra di codice e le possibili inefficienze che le astrazioni possono introdurre? Oppure usare semplicemente stringhe di query dirette è un metodo accettabile per gestire le interazioni con il database?


Forse puoi usare le procedure memorizzate per "avvolgere" i messaggi disordinati select- devi solo chiamare tali procedure con alcuni parametri di cui hai bisogno
k102

Risposte:


7

Quando hai troppe funzioni di query specializzate puoi provare a suddividerle in bit componibili. Per esempio

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

C'è anche una gerarchia di livelli di astrazione che potresti trovare utili da tenere a mente. Hai

  1. API mysql
  2. le tue funzioni mysql, come select ("select * from posts where foo = bar"); o forse più compostabile comeselect("posts")->where("foo = bar")->first(5)
  3. funzioni specifiche del dominio dell'applicazione, ad esempio posts()->joinWithComments()
  4. funzioni specifiche di una determinata pagina, ad esempio commentsToBeReviewed($currentUser)

Pagare molto in termini di facilità di manutenzione per rispettare questo ordine di astrazioni. Gli script delle pagine dovrebbero usare solo le funzioni di livello 4, le funzioni di livello 4 dovrebbero essere scritte in termini di funzioni di livello 3 e così via. È vero che questo richiede un po 'più di tempo in anticipo ma contribuirà a mantenere costanti i costi di manutenzione nel tempo (al contrario di "oh mio Dio vogliono un altro cambiamento !!!")


2
+1 Questa sintassi crea essenzialmente il tuo ORM. Se sei davvero preoccupato che le cose sull'accesso al database diventino complesse e non vuoi perdere molto tempo a armeggiare con i dettagli, suggerirei di utilizzare un framework Web maturo (ad esempio CodeIgniter ) che abbia già capito queste cose. O almeno prova ad usarlo per vedere che tipo di zucchero sintattico ti dà, in modo simile a quanto dimostrato da xpmatteo.
Hartley Brody,

5

La separazione delle preoccupazioni è un principio di cui vale la pena leggere, vedi l'articolo di Wikipedia su di esso.

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

Un altro principio di cui vale la pena leggere è l'accoppiamento:

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

Hai due preoccupazioni distinte, uno è il marshalling dei dati dal database e il secondo è il rendering di tali dati. In un'applicazione davvero semplice c'è probabilmente poco di cui preoccuparsi, hai strettamente accoppiato il tuo livello di gestione e accesso al database con il tuo livello di rendering, ma per le piccole app questo non è un grosso problema. Il problema è che le applicazioni Web tendono ad evolversi e se si desidera mai ridimensionare un'app Web, in qualsiasi modo, ad esempio prestazioni o funzionalità, si verificano alcuni problemi.

Diciamo che stai generando una pagina web di commenti generati dagli utenti. Arriva il capo dai capelli appuntiti e ti chiede di iniziare a supportare Native Apps, ad esempio iPhone / Android, ecc. Abbiamo bisogno di un output JSON, ora devi estrarre il codice di rendering che stava generando HTML. Quando hai fatto questo, ora hai una libreria di accesso ai dati con due motori di rendering e tutto va bene, hai ridimensionato funzionalmente. Potresti anche essere riuscito a mantenere tutto separato, ad esempio la logica aziendale dal rendering.

Arriva il capo e ti dice che ha un cliente che desidera visualizzare i post sul proprio sito Web, hanno bisogno di XML e hanno bisogno di circa 5000 richieste al secondo di prestazioni di picco. Ora devi generare XML / JSON / HTML. È possibile separare nuovamente il rendering, come prima. Tuttavia, ora è necessario aggiungere 100 server per ottenere comodamente le prestazioni necessarie. Ora il tuo database viene colpito da 100 server con forse dozzine di connessioni per server, ognuna delle quali è direttamente esposta a tre diverse app con requisiti diversi e query diverse ecc. Avere l'accesso al database su ogni macchina frontend è un rischio per la sicurezza e un aumento uno ma non ci andrò. Ora devi ridimensionare per le prestazioni, ogni app ha requisiti di memorizzazione nella cache diversi, vale a dire preoccupazioni diverse. Puoi provare a gestirlo in un livello strettamente accoppiato, ovvero accesso al database / logica aziendale / livello di rendering. Le preoccupazioni di ciascun livello stanno ora iniziando a intralciarsi l'una con l'altra, ad esempio i requisiti di memorizzazione nella cache dei dati dal database potrebbero essere molto diversi rispetto al livello di rendering, è probabile che la logica che hai nel livello aziendale si riversi nel SQL, ovvero spostarsi all'indietro o potrebbe spargersi in avanti nel livello di rendering, questo è uno dei maggiori problemi che ho visto con avere tutto in un livello, è come versare cemento armato nella tua applicazione e non in modo positivo.

Esistono modi standard per affrontare questi tipi di problemi, ad esempio la memorizzazione nella cache HTTP dei servizi Web (calamari / yts ecc.). La memorizzazione nella cache a livello di applicazione all'interno dei servizi Web stessi con qualcosa come memcached / redis. Incontrerai anche problemi quando inizi a ridimensionare il tuo database, ad esempio più host di lettura e un master, o dati condivisi tra host. Non vuoi che 100 host gestiscano varie connessioni al tuo database che differiscono in base alle richieste di scrittura o lettura o in un database ristretto se un utente "usera" esegue l'hashing in "[tabella / database] pippo" per tutte le richieste di scrittura.

La separazione delle preoccupazioni è tua amica, scegliere quando e dove farlo è una decisione architettonica e un po 'di arte. Evitare l'accoppiamento stretto di tutto ciò che si evolverà per avere requisiti molto diversi. Ci sono molte altre ragioni per mantenere le cose separate, ad esempio semplifica i test, l'implementazione delle modifiche, la sicurezza, il riutilizzo, la flessibilità ecc.


Capisco da dove vieni e non sono in disaccordo con qualsiasi cosa tu abbia detto, ma questa è una piccola preoccupazione in questo momento. Il progetto in questione è personale e la maggior parte del mio problema con il mio modello attuale proviene dall'istinto del mio programmatore per evitare l'accoppiamento stretto, ma sono davvero un principiante allo sviluppo complesso lato server, quindi la fine è andata un po ' sopra la mia testa. Tuttavia, +1 per quello che mi sembra un buon consiglio, anche se potrei non seguirlo interamente per questo progetto.
Alexis King,

Se quello che stai facendo rimarrà piccolo, lo terrei il più semplice possibile. Un buon principio da seguire qui è YAGNI .
Harry,

1

Suppongo che quando dici "la pagina stessa" intendi il file sorgente PHP che genera dinamicamente HTML.

Non eseguire query sul database e generare HTML nello stesso file di origine.

Il file di origine in cui si esegue una query sul database non è una "pagina", anche se è un file di origine PHP.

Nel file sorgente PHP in cui si crea dinamicamente il codice HTML, si effettuano semplicemente chiamate alle funzioni definite nel file sorgente PHP in cui si accede al database.


0

Il modello che utilizzo per la maggior parte dei progetti su scala media è il seguente:

  • Tutte le query SQL sono separate dal codice lato server, in una posizione separata.

    Lavorare con C # significa usare classi parziali, cioè mettere le query in un file separato, dato che quelle query saranno accessibili da una singola classe (vedi il codice sotto).

  • Quelle query SQL sono costanti . Questo è importante, poiché impedisce la tentazione di creare query SQL al volo (aumentando così il rischio di iniezione SQL e allo stesso tempo rendendo più difficile la revisione delle query in un secondo momento).

Approccio attuale in C #

Esempio:

// 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.
    }
}

Questo approccio ha il vantaggio di avere le query in un file separato. Ciò consente a un DBA di rivedere e modificare / ottimizzare le query e impegnare il file modificato nel controllo del codice sorgente senza essere in conflitto con i commit effettuati dagli sviluppatori.

Un vantaggio correlato è che il controllo del codice sorgente può essere configurato in modo da limitare l'accesso di DBA ai soli file che contengono le query e negare l'accesso al resto del codice.

È possibile fare in PHP?

A PHP mancano sia le classi parziali che le classi interne, quindi così com'è, non può essere implementato in PHP.

È possibile creare un file separato con una classe statica separata ( DemoQueries) contenente le costanti, dato che la classe sarà accessibile da qualsiasi luogo. Inoltre, al fine di evitare l'inquinamento dell'ambito globale, è possibile inserire tutte le classi di query in uno spazio dei nomi dedicato. Ciò creerà una sintassi piuttosto dettagliata, ma dubito che tu possa evitare la verbosità:

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.
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.