Introduzione
L'utilizzo di un database per gestire i dati dell'applicazione è una delle scelte più comuni per la persistenza dei dati. I database consentono l'archiviazione e il recupero rapidi delle informazioni, forniscono garanzie di integrità dei dati e offrono persistenza oltre la durata di una singola istanza dell'applicazione. Sono disponibili innumerevoli tipi di database per soddisfare i requisiti del tuo progetto e le tue preferenze.
Tuttavia, lavorare direttamente con i database dalla tua applicazione non è sempre facile. Le differenze nel modo in cui le strutture di dati sono rappresentate spesso portano a sfide. Anche la difficoltà nell'esprimere sottigliezze sulle relazioni tra entità diverse può causare problemi. Per risolvere questo problema, sono stati creati molti strumenti diversi che aiutano a fungere da interfaccia tra l'applicazione principale e il livello dati.
In questa guida, esamineremo alcune delle differenze che sorgono tra tre approcci comuni:SQL non elaborato, generatori di query e ORM (mapper relazionali di oggetti). Confronteremo alcuni dei vantaggi e degli svantaggi di ciascun approccio e poi finiremo con un glossario di termini comunemente usati per aiutarti a familiarizzare con alcuni concetti chiave.
Come sintesi semplificata, ecco una visione generale dei punti di forza e di debolezza di ciascun approccio:
Approccio | Incentrato su database / programmazione | Gestione pratica | Livello di astrazione | Livello di complessità |
---|---|---|---|---|
SQL non elaborato | orientato al database | alto | nessuno | basso |
Costruttori di query | misti | basso | basso | basso |
ORM | orientato alla programmazione | basso | alto | alto |
Gestione dei dati con SQL grezzo o un altro linguaggio di query nativo del database
Alcune applicazioni si interfacciano direttamente con il database scrivendo ed eseguendo query utilizzando la lingua nativa supportata dal motore di database. Spesso, un driver di database è tutto ciò che serve per connettersi, autenticarsi e comunicare con l'istanza del database.
Gli sviluppatori possono inviare query scritte nella lingua madre del database tramite la connessione. In cambio, il database fornirà i risultati della query, anche in uno dei suoi formati nativi. Per molti database relazionali, il linguaggio di interrogazione preferito è SQL.
La maggior parte dei database relazionali, così come alcuni database non relazionali, supportano il linguaggio di query strutturato, noto anche come SQL, per creare ed eseguire query potenti. SQL è stato utilizzato per gestire i dati dagli anni '70, quindi è ben supportato e standardizzato in una certa misura.
Vantaggi delle query native
L'uso di SQL o di un altro linguaggio nativo del database ha alcuni chiari vantaggi.
Un vantaggio è che gli sviluppatori scrivono e gestiscono le query del database e gestiscono i risultati in modo esplicito. Anche se questo può richiedere molto lavoro aggiuntivo, significa che ci sono poche sorprese in termini di ciò che il database sta memorizzando, come rappresenta i tuoi dati e come fornirà quei dati quando verranno recuperati in seguito. La mancanza di astrazione significa che ci sono meno "parti mobili" che possono portare all'incertezza.
Un esempio di questo è la performance. Mentre sofisticati livelli di astrazione generano query SQL traducendo istruzioni di programmazione, l'SQL generato può essere molto inefficiente. Clausole non necessarie, query eccessivamente ampie e altri incidenti possono portare a operazioni di database lente che possono essere fragili e difficili da eseguire il debug. Scrivendo in modo nativo in SQL, puoi utilizzare tutta la tua conoscenza del dominio e il buon senso per evitare molte classi di problemi di query
Un altro motivo per utilizzare le query native del database è la flessibilità. È probabile che nessuna astrazione possa essere flessibile come il linguaggio di query del database nativo. Livelli più elevati di astrazione tentano di colmare il divario tra due diversi paradigmi, che possono limitare i tipi di operazioni che possono esprimere. Quando scrivi in SQL non elaborato, tuttavia, puoi sfruttare tutte le funzionalità del tuo motore di database ed esprimere query più complesse.
Svantaggi delle query native
Sebbene l'interrogazione nativa abbia alcuni punti di forza definiti, non è priva di problemi.
Quando si interagisce con un database da un'applicazione che utilizza SQL semplice, è necessario comprendere la struttura dei dati sottostante per comporre query valide. Sei completamente responsabile della traduzione tra i tipi di dati e le strutture utilizzati dalla tua applicazione e le costruzioni disponibili all'interno del sistema di database.
Un'altra cosa da tenere a mente quando si lavora con SQL non elaborato è che dipende interamente da te gestire la sicurezza del tuo input. Ciò è particolarmente vero se stai archiviando dati forniti da utenti esterni, dove un input appositamente predisposto potrebbe indurre il tuo database a esporre informazioni che non avevi intenzione di consentire.
Questo tipo di exploit è chiamato SQL injection ed è un potenziale problema ogni volta che l'input dell'utente può influenzare lo stato del database. Strumenti di astrazione più elevati spesso disinfettano automaticamente l'input dell'utente, aiutandoti a evitare questo tipo di problemi.
Lavorare con linguaggi di query nativi significa quasi sempre comporre query con stringhe regolari. Questo può essere un processo doloroso nei casi in cui è necessario sfuggire all'input e concatenare le stringhe per creare una query valida. Le operazioni del tuo database possono essere intrappolate in molti livelli di manipolazione delle stringhe che hanno un alto potenziale di alterazione accidentale dei dati.
Riepilogo query native
Sebbene in questa sezione abbiamo parlato principalmente di SQL, la maggior parte delle informazioni qui si applica ugualmente bene a qualsiasi linguaggio di query di database nativo. Per riassumere, l'SQL grezzo o l'uso diretto di qualsiasi linguaggio di query equivalente ti avvicina alle astrazioni utilizzate dal database per archiviare e gestire i dati, ma ti costringe a fare tutto il lavoro pesante della gestione dei dati manualmente.
Gestione dei dati con i generatori di query
Un approccio alternativo all'utilizzo di linguaggi di query nativi del database come SQL consiste nell'utilizzare uno strumento o una libreria denominata generatore di query per comunicare con il database.
Cosa sono i costruttori di query SQL?
Un generatore di query SQL aggiunge un livello di astrazione al di sopra dei linguaggi di query nativi del database grezzo. Lo fanno formalizzando i modelli di query e fornendo metodi o funzioni che aggiungono sanificazione dell'input ed evadono automaticamente gli elementi per una più facile integrazione nelle applicazioni.
Le strutture e le azioni supportate dal livello del database sono ancora abbastanza riconoscibili quando si utilizzano i generatori di query SQL. Ciò ti consente di lavorare con i dati a livello di codice rimanendo comunque relativamente vicino ai dati.
Di solito, i generatori di query forniscono un'interfaccia che utilizza metodi o funzioni per aggiungere una condizione a una query. Concatenando i metodi insieme, gli sviluppatori possono comporre query di database complete da queste singole "clausole".
Vantaggi dei costruttori di query SQL
Poiché i generatori di query utilizzano le stesse costruzioni (metodi o funzioni) del resto dell'applicazione, gli sviluppatori spesso le trovano più facili da gestire a lungo termine rispetto alle query di database non elaborate scritte come stringhe. È semplice distinguere tra operatori e dati ed è facile scomporre le query in blocchi logici che gestiscono parti specifiche di una query.
Per alcuni sviluppatori, un altro vantaggio dell'utilizzo di un generatore di query SQL è che non sempre nasconde il linguaggio di query sottostante. Sebbene le operazioni possano utilizzare metodi anziché stringhe, possono essere abbastanza trasparenti, il che rende più facile per coloro che hanno familiarità con il database capire cosa farà un'operazione. Questo non è sempre il caso quando si utilizzano livelli di astrazione maggiori.
I generatori di query SQL spesso supportano anche più backend di dati, ad esempio astraendo alcune delle sottili differenze nei vari database relazionali. Ciò consente di utilizzare gli stessi strumenti per progetti che utilizzano database diversi. Potrebbe persino rendere leggermente più semplice la migrazione a un nuovo database.
Svantaggi dei costruttori di query SQL
I generatori di query SQL presentano alcuni degli stessi svantaggi dei linguaggi di query nativi.
Una critica popolare è che i generatori di query SQL richiedono ancora di comprendere e tenere conto delle strutture e delle capacità del database. Questa non è un'astrazione abbastanza utile per alcuni sviluppatori. Ciò significa che devi avere una discreta conoscenza di SQL oltre alla sintassi e alle capacità specifiche del generatore di query stesso.
Inoltre, i generatori di query SQL richiedono ancora di definire il modo in cui i dati recuperati sono correlati ai dati dell'applicazione. Non esiste una sincronizzazione automatica tra i tuoi oggetti in memoria e quelli nel database.
Sebbene i generatori di query spesso emulino il linguaggio di query con cui sono progettati per funzionare, il livello aggiuntivo di astrazione può significare che a volte alcune operazioni non sono possibili utilizzando i metodi forniti. Di solito, esiste una modalità "grezza" per inviare query direttamente al back-end, bypassando l'interfaccia tipica del generatore di query, ma questo elude il problema anziché risolverlo.
Riepilogo dei costruttori di query SQL
Nel complesso, i generatori di query SQL offrono un sottile livello di astrazione che prende di mira in modo specifico alcuni dei principali punti deboli dell'utilizzo diretto dei linguaggi nativi del database. I generatori di query SQL funzionano quasi come un sistema di modelli per le query, consentendo agli sviluppatori di attraversare il confine tra il lavoro diretto con il database e l'aggiunta di ulteriori livelli di astrazione.
Gestione dei dati con ORM
Un gradino più in alto nella gerarchia di astrazione sono gli ORM. Gli ORM generalmente mirano a un'astrazione più completa con la speranza di un'integrazione più fluida con i dati dell'applicazione.
Cosa sono gli ORM?
I mappatori relazionali a oggetti, o ORM, sono pezzi di software dedicati alla traduzione tra le rappresentazioni dei dati nei database relazionali e la rappresentazione in memoria utilizzata con la programmazione orientata agli oggetti (OOP). L'ORM fornisce un'interfaccia orientata agli oggetti per i dati all'interno del database, tentando di utilizzare concetti di programmazione familiari e ridurre la quantità di codice standard necessario per accelerare lo sviluppo.
In generale, gli ORM fungono da livello di astrazione inteso ad aiutare gli sviluppatori a lavorare con i database senza modificare drasticamente il paradigma orientato agli oggetti. Questo può essere utile riducendo il carico mentale dell'adattamento alle specifiche del formato di archiviazione di un database.
In particolare, gli oggetti nella programmazione orientata agli oggetti tendono a codificare molto stato al loro interno e possono avere relazioni complesse con altri oggetti attraverso l'ereditarietà e altri concetti OOP. Mappare queste informazioni in modo affidabile in un paradigma relazionale orientato alla tabella spesso non è semplice e può richiedere una buona comprensione di entrambi i sistemi. Gli ORM tentano di alleggerire questo onere automatizzando parte di questa mappatura e fornendo interfacce espressive ai dati all'interno del sistema.
Le sfide degli ORM sono specifiche della programmazione orientata agli oggetti e database relazionali?
Per definizione, gli ORM sono progettati specificamente per interfacciarsi tra linguaggi applicativi orientati agli oggetti e database relazionali. Tuttavia, il tentativo di mappare e tradurre tra le astrazioni della struttura dei dati utilizzate nei linguaggi di programmazione e quelle utilizzate dagli archivi di database è un problema più generale che può esistere ogni volta che le astrazioni non si allineano in modo pulito.
A seconda del paradigma di programmazione (orientato agli oggetti, funzionale, procedurale, ecc.) e del tipo di database (relazionale, documento, valore-chiave, ecc.), possono essere utili diverse quantità di astrazione. Spesso, la complessità delle strutture dati all'interno dell'applicazione determina quanto sia facile interfacciarsi con l'archivio dati.
La programmazione orientata agli oggetti tende a produrre molte strutture con stato e relazioni significative di cui è necessario tenere conto. Alcuni altri paradigmi di programmazione sono più espliciti su dove viene archiviato lo stato e su come viene gestito. Ad esempio, i linguaggi puramente funzionali non consentono lo stato mutevole, quindi lo stato è spesso un input per funzioni o oggetti che generano un nuovo stato. Questa netta separazione dei dati dalle azioni, così come l'esplicitezza dei cicli di vita degli stati, possono aiutare a semplificare l'interazione con il database.
In entrambi i casi, è spesso disponibile l'opzione di interfacciarsi con un database tramite un software che mappa tra due diverse rappresentazioni. Quindi, mentre gli ORM descrivono un sottoinsieme specifico di questi con sfide uniche, la mappatura tra la memoria dell'applicazione e l'archiviazione persistente spesso richiede considerazione indipendentemente dai dettagli.
ORM di record attivi vs data mapper
Diversi ORM utilizzano strategie diverse per mappare tra applicazioni e strutture di database. Le due categorie principali sono il modello di record attivo e il modello di mappatura dati .
Il modello di record attivo tenta di incapsulare i dati del database all'interno della struttura degli oggetti all'interno del codice. Gli oggetti contengono metodi per salvare, aggiornare o eliminare dal database e le modifiche agli oggetti devono essere riflesse facilmente nel database. In generale, un oggetto record attivo nell'applicazione rappresenta un record all'interno di un database.
Le implementazioni di record attivi consentono di gestire il database creando e connettendo classi e istanze all'interno del codice. Poiché generalmente associano le istanze della classe direttamente ai record del database, è facile concettualizzare cosa c'è nel tuo database se capisci quali oggetti sono usati nel tuo codice.
Sfortunatamente, questo può anche comportare alcuni importanti svantaggi. Le applicazioni tendono ad essere strettamente associate al database, il che può causare problemi quando si tenta di migrare a un nuovo database o anche durante il test del codice. Il tuo codice tende a fare affidamento sul database per colmare le lacune che sono state scaricate dai tuoi oggetti. La traduzione "magica" tra questi due domini può anche portare a problemi di prestazioni poiché il sistema cerca di mappare senza problemi oggetti complessi alla struttura dei dati sottostante.
Il pattern del data mapper è l'altro pattern ORM comune. Come il modello di record attivo, il data mapper tenta di agire come un livello indipendente tra il codice e il database che funge da mediatore tra i due. Tuttavia, invece di cercare di integrare perfettamente oggetti e record di database, si concentra sul tentativo di disaccoppiare e tradurre tra di loro lasciando che ciascuno esista in modo indipendente. Questo può aiutare a separare la tua logica aziendale dai dettagli relativi al database che si occupano di mappature, rappresentazione, serializzazione e così via.
Quindi, anziché lasciare che il sistema ORM capisca come eseguire il mapping tra gli oggetti e le tabelle del database, lo sviluppatore è responsabile della mappatura esplicita tra i due. Questo può aiutare a evitare un accoppiamento stretto e operazioni dietro le quinte a scapito di molto più lavoro per capire le mappature appropriate.
Vantaggi degli ORM
Gli ORM sono popolari per molte ragioni.
Aiutano ad astrarre il dominio dei dati sottostante a qualcosa su cui è facile ragionare nel contesto dell'applicazione. Piuttosto che pensare all'archiviazione dei dati come a un sistema indipendente, gli ORM ti aiutano ad accedere e gestire i sistemi di dati come un'estensione del tuo lavoro attuale. Questo può aiutare gli sviluppatori a lavorare più velocemente sulla logica aziendale principale invece di impantanarsi nelle sfumature dei loro backend di archiviazione.
Un altro effetto collaterale di ciò è che gli ORM rimuovono gran parte del boilerplate necessario per interfacciarsi con i database. Gli ORM sono spesso dotati di strumenti di migrazione che consentono di gestire le modifiche allo schema del database in base alle modifiche apportate al codice. Non è necessario determinare in anticipo lo schema del database perfetto se l'ORM può aiutare a gestire le modifiche alla struttura del database. Le modifiche all'applicazione e al database sono spesso la stessa cosa o sono strettamente correlate, il che aiuta a tenere traccia delle modifiche al database mentre apporti modifiche al codice.
Svantaggi degli ORM
Gli ORM non sono privi di difetti. In molti casi derivano dalle stesse decisioni che rendono utili gli ORM.
Uno dei problemi fondamentali con gli ORM è il tentativo di nascondere i dettagli del backend del database. Questo offuscamento rende più facile lavorare con gli ORM in casi semplici o su scale temporali ridotte, ma spesso porta a problemi su tutta la linea man mano che la complessità cresce.
L'astrazione non è mai completa al 100% e il tentativo di utilizzare un ORM senza comprendere il linguaggio di query sottostante o la struttura del database spesso porta a presupposti problematici. Ciò può rendere difficile o impossibile il debug e l'ottimizzazione delle prestazioni.
Forse il problema più noto del lavoro con gli ORM è il mismatch di impedenza relazionale con gli oggetti, un termine usato per descrivere la difficoltà di tradurre tra la programmazione orientata agli oggetti e il paradigma relazionale utilizzato dai database relazionali. Le incompatibilità tra i modelli di dati utilizzati da queste due categorie di tecnologia significano che è necessaria un'astrazione aggiuntiva e imperfetta ad ogni aumento di complessità. Il disadattamento di impedenza relazionale oggetto è stato chiamato il Vietnam dell'informatica (in riferimento alla guerra del Vietnam) a causa della sua tendenza ad aumentare la complessità nel tempo e portare a situazioni in cui le strade per il successo o per cambiare rotta sono difficili o impossibili.
In generale, gli ORM tendono ad essere più lenti delle alternative, specialmente con query complesse. Gli ORM spesso generano query complicate per operazioni di database relativamente semplici, poiché utilizzano modelli generali che devono essere sufficientemente flessibili per gestire altri casi. La dipendenza dall'ORM per fare la cosa giusta in tutte le circostanze può portare a errori costosi che possono essere difficili da recuperare.
Riepilogo degli ORM
Gli ORM possono essere utili astrazioni che semplificano notevolmente il lavoro con i database. Possono aiutarti a progettare e ripetere rapidamente e colmare le differenze concettuali tra la logica dell'applicazione e le strutture del database. Tuttavia, molti di questi vantaggi agiscono come un'arma a doppio taglio. Possono impedirti di comprendere i tuoi database e possono rendere difficile il debug, cambiare i paradigmi o aumentare le prestazioni.
Glossario
Quando si lavora con tecnologie che si interfacciano tra database e applicazioni, è possibile che si verifichi una terminologia con cui non si ha familiarità. In questa sezione, esamineremo brevemente alcuni dei termini più comuni che potresti incontrare, alcuni dei quali sono stati trattati in precedenza in questo articolo e altri no.
- Mappatura dati: Un data mapper è un modello di progettazione o un pezzo di software che mappa le strutture dei dati di programmazione a quelle archiviate in un database. I data mapper tentano di sincronizzare le modifiche tra le due origini mantenendole indipendenti l'una dall'altra. Il mapper stesso è responsabile del mantenimento di una traduzione funzionante, consentendo agli sviluppatori di iterare le strutture dei dati dell'applicazione senza preoccuparsi della rappresentazione del database.
- Driver del database: Un driver di database è un software progettato per incapsulare e abilitare le connessioni tra un'applicazione e un database. I driver di database astraggono i dettagli di basso livello su come creare e gestire le connessioni e forniscono un'interfaccia unificata e programmatica al sistema di database. In genere, i driver di database sono il livello di astrazione più basso utilizzato dagli sviluppatori per interagire con i database, con strumenti di livello superiore che si basano sulle capacità fornite dal driver.
- Attacco di iniezione: Un attacco injection è un attacco in cui un utente malintenzionato tenta di eseguire operazioni di database indesiderate utilizzando input appositamente predisposti nei campi dell'applicazione rivolti all'utente. Spesso viene utilizzato per recuperare dati che non dovrebbero essere accessibili o per eliminare o alterare informazioni nel database.
- ORM: Gli ORM, o mappatori relazionali a oggetti, sono livelli di astrazione che traducono tra le rappresentazioni dei dati utilizzate nei database relazionali e la rappresentazione in memoria utilizzata con la programmazione orientata agli oggetti. L'ORM fornisce un'interfaccia orientata agli oggetti per i dati all'interno del database, tentando di ridurre la quantità di codice e di utilizzare archetipi familiari per accelerare lo sviluppo.
- Disadattamento dell'impedenza relazionale dell'oggetto: La mancata corrispondenza dell'impedenza relazionale con gli oggetti si riferisce alla difficoltà di tradurre tra un'applicazione orientata agli oggetti e un database relazionale. Poiché le strutture dei dati variano in modo significativo, può essere difficile mutare e trascrivere in modo fedele e performante le strutture dei dati programmatici nel formato utilizzato dal back-end di archiviazione.
- Quadro di persistenza: Un framework di persistenza è un livello di astrazione del middleware sviluppato per colmare il divario tra i dati del programma e i database. I framework di persistenza possono anche essere ORM se l'astrazione che utilizzano associa oggetti a entità relazionali.
- Generatore di query: Un generatore di query è un livello di astrazione che aiuta gli sviluppatori ad accedere e controllare i database fornendo un'interfaccia controllata che aggiunge funzionalità di usabilità, sicurezza o flessibilità. In genere, i generatori di query sono relativamente leggeri, si concentrano sull'agevolazione dell'accesso ai dati e sulla rappresentazione dei dati e non tentano di tradurre i dati in un paradigma di programmazione specifico.
- SQL: SQL, o linguaggio di query strutturato, è un linguaggio specifico del dominio sviluppato per la gestione dei sistemi di gestione di database relazionali. Può essere utilizzato per interrogare, definire e manipolare i dati all'interno di un database, nonché le relative strutture organizzative. SQL è onnipresente tra i database relazionali.
Conclusione
In questo articolo, abbiamo esaminato alcune diverse opzioni per l'interfacciamento con il database dalla tua applicazione. Abbiamo esaminato i diversi livelli di astrazione e la flessibilità offerta dall'utilizzo di linguaggi di query nativi del database come SQL, utilizzando un generatore di query per creare query in modo sicuro e ORM per fornire un livello di astrazione più completo.
Ognuno di questi approcci ha i suoi usi e alcuni possono essere più adatti per determinati tipi di applicazioni rispetto ad altri. È importante comprendere i requisiti della tua applicazione, la conoscenza del database della tua organizzazione e i costi delle astrazioni (o della loro mancanza) che scegli di implementare. Nel complesso, la comprensione di ogni approccio ti darà le migliori possibilità di selezionare l'opzione che si adatta bene ai tuoi progetti.