Quando dovrei usare DBIx :: Class di Perl?


17

DBIx :: Class è una popolare interfaccia Perl per qualsiasi database a cui è possibile connettersi tramite DBI . Esiste una buona documentazione per i dettagli tecnici, ma scarse informazioni sul suo corretto utilizzo (comprese le situazioni in cui probabilmente non lo si desidera).

In molte situazioni, le persone lo cercano riflessivamente perché pensano di doverlo utilizzare per tutto ciò che coinvolge un database. Tuttavia, il più delle volte l'ho visto abusato al punto in cui diventa un punto dolente. La mia domanda durante le revisioni del codice e dell'architettura è sempre "Che vantaggio ti sta dando Foo?" Molto spesso, gli sviluppatori che vedo in queste situazioni non possono dare una risposta coerente a questo. Ma poi, spesso non capiscono anche il semplice SQL.

Negli ultimi due mesi ho chiesto alla gente "Perché usi DBIx :: Class?" e ho ricevuto solo una buona risposta (e quella persona è stata anche in grado di rispondere al follow-up "Quando non la useresti"). Peter Rabbitson, lo sviluppatore principale, si è avvicinato a una risposta nella sua intervista su FLOSS Weekly , ma è un po 'sepolto nel mezzo dell'intervista.

Quindi, come posso decidere se l'utilizzo di DBIx :: Class è appropriato per un progetto?


3
Stai scrivendo un altro libro? :)
simbabque

1
Nella mia esperienza il dolore arriva quando DBIC viene utilizzato in situazioni in cui è eccessivo. E, nonostante sia molto potente, è spesso eccessivo perché le persone usano solo le funzionalità di base (generazione e join SQL) e non hanno bisogno di nient'altro. Ecco perché ho scritto DBIx :: Lite, che fornisce queste funzionalità di base e non richiede che uno schema sia codificato.
Alessandro,

Risposte:


24

Prima di rispondere alla domanda, penso che alcuni retroscena siano in ordine.

Il nocciolo del problema

Dopo anni di interviste e assunzioni di sviluppatori, ho imparato due cose:

  1. La stragrande maggioranza degli sviluppatori ha pochissima esperienza nella progettazione di database.

  2. Ho notato una chiara correlazione tra coloro che non comprendono i database e quelli che odiano gli ORM.

(Nota: e sì, so che ci sono quelli che capiscono molto bene i database che odiano gli ORM)

Quando la gente non capisce il motivo per cui le chiavi esterne sono importanti, perché non incorporare il nome del produttore del itemtavolo, o perché customer.address1, customer.address2e customer.address3campi non sono una buona idea, aggiungendo un ORM per rendere più facile per loro di bug del database di scrittura non aiuterà nulla.

Invece, con un database ben progettato e un caso d'uso OLTP, gli ORM sono d'oro. Gran parte del lavoro grugnito va via e con strumenti come DBIx :: Class :: Schema :: Loader , posso passare da un buon schema di database al funzionamento del codice Perl in pochi minuti. Vorrei citare la regola di Pareto e dire che l'80% dei miei problemi è stato risolto con il 20% del lavoro, ma in realtà trovo che i benefici siano ancora maggiori.

Abusare della soluzione

Un altro motivo per cui alcune persone odiano gli ORM è perché lasceranno perdere l'astrazione. Consideriamo il caso comune delle app Web MVC. Ecco qualcosa che comunemente vediamo (pseudo-codice):

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,
   });
}

Le persone scrivono percorsi del controller in questo modo e si danno una pacca sulla spalla, pensando che sia un buon codice pulito. Sarebbero sbalorditi dal codice SQL nei loro controller, ma hanno fatto poco più che esporre una diversa sintassi SQL. Il loro codice ORM deve essere inserito in un modello e quindi possono farlo:

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

Sai cosa è successo adesso? Hai incapsulato correttamente il tuo modello, non hai esposto l'ORM e in seguito, quando scopri che potresti recuperare quei dati da una cache anziché dal database, non è necessario modificare il codice del controller (ed è più facile scrivere test per questo e riutilizzare la logica).

In realtà, ciò che accade è che le persone perdono il loro codice ORM su tutti i loro controller (e viste) e quando colpiscono problemi di scalabilità, iniziano a incolpare l'ORM anziché la loro architettura. L'ORM ottiene un brutto rap (lo vedo ripetutamente per molti clienti). Invece, nascondi quell'astrazione in modo che quando hai effettivamente raggiunto i limiti ORM, puoi scegliere le soluzioni appropriate per il tuo problema piuttosto che lasciare che il codice sia così strettamente accoppiato all'ORM che sei legato.

Rapporti e altre limitazioni

Come Rob Kinyon ha chiarito sopra, la segnalazione tende ad essere una debolezza degli ORM. Questo è un sottoinsieme di un problema più grande in cui SQL o SQL complessi che si estendono su più tabelle a volte non funzionano bene con gli ORM. Ad esempio, a volte l'ORM impone un tipo di join che non desidero e non so come risolverlo. O forse voglio usare un suggerimento sull'indice in MySQL, ma non è facile . O a volte l'SQL è così complicato che sarebbe più bello scrivere l'SQL piuttosto che l'astrazione fornita.

Questo è uno dei motivi per cui ho iniziato a scrivere DBIx :: Class :: Report . Finora funziona bene e risolve la maggior parte dei problemi che le persone hanno qui (purché stiano bene con un'interfaccia di sola lettura). E mentre sembra una stampella, in realtà, fintanto che non perdi la tua astrazione (come spiegato nella sezione precedente), rende il lavoro DBIx::Classancora più semplice.

Quindi quando sceglierei DBIx :: Class?

Per me, lo sceglierei la maggior parte delle volte che ho bisogno di un'interfaccia per un database. L'ho utilizzato per anni. Tuttavia, potrei non sceglierlo per un sistema OLAP e i programmatori più recenti avranno sicuramente delle difficoltà. Inoltre, trovo spesso che ho bisogno di meta-programmazione e mentre DBIx::Classfornisce gli strumenti, sono molto scarsamente documentati.

La chiave per utilizzare DBIx::Classcorrettamente è la stessa della maggior parte degli ORM:

  1. Non perdere l'astrazione.

  2. Scrivi i tuoi dannati test.

  3. Sapere come passare a SQL, se necessario.

  4. Scopri come normalizzare un database.

DBIx::Class, una volta appreso, si occuperà della maggior parte del tuo sollevamento pesante per te e ti semplifica la scrittura rapida di applicazioni.


1
Forse puoi aggiungere un altro elenco per quando non lo utilizzeresti. :)
brian d foy il

1
Questo è ovvio per te, ma probabilmente non è ovvio per molti lettori (dico, dopo aver passato anni #dbix-classe anni #catalyst) - la chiave del bit "non perdere l'astrazione" è che ogni singola cosa con cui stai lavorando in DBIC è una sottoclasse di qualcosa che fornisce il comportamento di cookie cutter. Sei fortemente incoraggiato ad aggiungere metodi alle tue sottoclassi e, a meno che tu non stia facendo un lavoro di domande e risposte, solo i metodi che hai scritto dovrebbero far parte della tua interfaccia pubblica.
Hobbs

@hobbs: In effetti, è lì che vedo le persone sbagliare di più in questo ed è il modo in cui si bloccano con DBIC. Spesso presumiamo che le persone sappiano cosa stanno facendo nel piccolo, ma lo scopriranno nel grande.
brian d foy,

9

Per sapere quando usare qualcosa, è importante capire qual è lo scopo della cosa. Qual è l'obiettivo del prodotto.

DBIx :: Class è un ORM - Mappatore relazionale oggetto. Un ORM prende le strutture di dati del database relazionale basato su set / relazionali e le mappa su una struttura ad oggetti. La mappatura tradizionale è un oggetto per riga, usando la struttura della tabella come descrizione della classe. Le relazioni padre-figlio nel database vengono trattate come relazioni di contenimento tra oggetti.

Questo è il nocciolo della questione. Ma ciò non ti aiuta a decidere se utilizzare un ORM. Gli ORM sono utili soprattutto quando sono vere le seguenti condizioni:

  • Si utilizza un database relazionale.
  • I tuoi dati sono ampiamente utilizzati per OLTP (elaborazione delle transazioni online).
  • Non scrivi rapporti all'interno della tua applicazione.

Il più grande punto di forza di un ORM è la costruzione di un buon SQL per camminare su un grafico ad albero sovrapposto alla struttura dei dati relazionali. L'SQL è spesso peloso e complesso, ma è il prezzo di gestione della mancata corrispondenza dell'impedenza.

Mentre gli ORM sono molto bravi nella scrittura di SQL di estrazione di righe, sono molto scarsi nella scrittura di SQL di confronto. Questo è il tipo di SQL su cui sono basati i report. Questo tipo di regole di confronto viene creato utilizzando strumenti diversi, non un ORM.

Esistono molti ORM in varie lingue, diversi in Perl. Altri mappatori sono Class :: DBI e Rose :: DB. DBIx :: Class è spesso considerato migliore degli altri soprattutto in base ai suoi risultati. Questo è un concetto in cui la generazione SQL è separata dall'esecuzione SQL.


Aggiornamento : in risposta a Ovidio, DBIx :: Class (tramite SQL :: Abstract) offre la possibilità di specificare sia quali colonne restituire sia quali suggerimenti sull'indice utilizzare.

In generale, tuttavia, se si desidera farlo, è meglio non utilizzare un ORM per quella specifica query. Ricorda: lo scopo principale di un ORM è mappare le righe di una tabella sugli oggetti di una classe i cui attributi sono le colonne della tabella. Se stai solo popolando alcuni degli attributi, i potenziali utenti di quell'oggetto non sapranno quali attributi vengono popolati o meno. Ciò porta a un'orribile programmazione difensiva e / o a un odio generale verso gli ORM.

Quasi sempre, il desiderio di utilizzare suggerimenti sull'indice o limitare quali colonne vengono restituite è un'ottimizzazione della velocità e / o una query di aggregazione.

  • Le query aggregate sono il caso d'uso per cui gli ORM NON sono progettati. Mentre DBIx :: Class può creare query di aggregazione, non stai creando un grafico a oggetti, quindi usa direttamente DBI.
  • Le ottimizzazioni delle prestazioni vengono utilizzate perché i dati da interrogare sono troppo grandi per il database sottostante, indipendentemente da come vi si accede. La maggior parte dei database relazionali sono ideali per le tabelle con righe fino a 1-3 MM che scorrono dagli SSD in cui la maggior parte dei dati + indici si adatta alla RAM. Se la tua situazione è più grande di così, allora ogni database relazionale avrà problemi.

Sì, un ottimo DBA può far funzionare le tabelle con 100 MM + righe in Oracle o SQL * Server. Se stai leggendo questo, non hai un ottimo DBA nello staff.

Detto questo, un buon ORM non si limita a creare grafici di oggetti, ma fornisce anche una definizione introspettibile del database. Puoi usarlo per creare query ad hoc e utilizzarle come faresti con DBI, senza creare il grafico degli oggetti.


Penso che quasi tutto ciò che vedo scriva rapporti, motivo per cui le persone devono abbandonare le domande manuali (e questo è il dolore).
brian d foy,

1
Non capisco perché è necessario passare alle query manuali per i report. Ho creato alcuni rapporti piuttosto complessi usando DBIC. Naturalmente, spesso comporta la creazione di un enorme set di risultati personalizzati con un uso intenso di "prefetch" e "join".
Dave Cross,

Dave: l'SQL manuale può essere molto più semplice da scrivere e per assicurarti di estrarre solo i sette campi necessari da tre tabelle e rappresentarli in una singola riga. Inoltre, è molto più semplice fornire suggerimenti durante la scrittura di SQL non elaborato.
Curtis Poe,

> per essere sicuro di estrarre solo i sette campi necessari Sì, ecco a cosa servono le "colonne" attr per le ricerche di ResultSet. Gli unici argomenti validi che ho ascoltato o visto per eseguire SQL non elaborati sono: 1. Sottoquery estremamente complesse, che di solito è il prodotto di una tabella / DB mal progettato 2. Esecuzione di DDL o altre operazioni "do", che DBIC non è davvero costruito per. 3. Cercare di hackerare i suggerimenti sull'indice. Ancora una volta, questo è più un punto debole del RDBMS, ma a volte deve essere fatto. Ed è possibile solo aggiungere quel tipo di funzionalità in DBIC.
Brendan Byrd,

8

Come uno dei principali sviluppatori della piattaforma di e-commerce di Interchange6 (e della motosega dello schema principale) ho un'esperienza piuttosto approfondita con DBIC. Ecco alcune delle cose che lo rendono una piattaforma così eccezionale:

  • Il generatore di query consente di scrivere una volta per molti motori di database (e più versioni di ciascuno). Attualmente supportiamo Interchange6 con MySQL, PostgreSQL e SQLite e aggiungeremo supporto per più motori una volta che avremo tempo e risorse. Attualmente ci sono solo due percorsi di codice nell'intero progetto che hanno un codice aggiuntivo per far fronte alle differenze tra i motori e ciò è dovuto esclusivamente alla mancanza di una specifica funzione del database (SQLite è carente in alcuni punti) o all'idiozia di MySQL che è cambiata il modo in cui la sua funzione LEAST gestisce NULL tra due versioni minori.

  • Query predefinite significano che posso creare metodi semplici che possono essere chiamati (con o senza args) dal codice dell'applicazione, quindi mantengo le mie query principalmente all'interno della definizione dello schema invece di sporcare il mio codice dell'applicazione.

  • La generazione di query compostabili consente di suddividere le query in piccole query predefinite comprensibili e quindi concatenarle per creare query complesse che sarebbero difficili da mantenere a lungo termine in entrambi i DBIC (anche peggio in SQL puro) se fossero costruite in un unico passaggio.

  • Schema :: Loader ci ha permesso di utilizzare DBIC con applicazioni legacy che danno una nuova prospettiva di vita e un percorso molto più semplice verso il futuro.

  • Plugin DBIC, DeploymentHandler & Migration aggiungono immensamente al set di strumenti che mi semplificano la vita.

Una delle enormi differenze tra DBIC e la maggior parte delle altre piattaforme simili a ORM / ORM è che sebbene cerchi di guidarti nel suo modo di fare le cose, non ti impedisce di fare cose folli che ti piacciono:

  • È possibile utilizzare le funzioni SQL e le procedure memorizzate che DBIC non conosce fornendo semplicemente il nome della funzione come chiave nella query (può anche essere divertente quando si tenta di utilizzare LEAST in MySQL ^^ ma non è colpa di DBIC) .

  • SQL letterale può essere utilizzato quando non esiste un "modo DBIC" per fare qualcosa e il risultato restituito è ancora racchiuso in simpatiche classi con accessori.

TL; DR probabilmente non mi preoccuperei di usarlo per applicazioni davvero semplici con solo un paio di tabelle, ma quando ho bisogno di gestire qualcosa di più complesso, specialmente dove la compatibilità tra motori e la manutenibilità a lungo termine sono fondamentali, DBIC è generalmente il mio percorso preferito.


7

(Prima di iniziare, dovrei dire che confronta solo i wrapper DBI basati su DBIC, DBI e Mojo. Non ho esperienza con altri ORM Perl e quindi non li commenterò direttamente).

DBIC fa molte cose molto bene. Non ne sono un grande utilizzatore, ma conosco la teoria. Fa un buon lavoro di generazione di SQL e soprattutto (come mi è stato detto) di gestire i join, ecc. Può anche fare il prefetching di altri dati correlati.

Il vantaggio principale a mio avviso è la possibilità di utilizzare DIRETTAMENTE i risultati come classe di modello. Questo è altrimenti noto come "aggiunta di metodi di set di risultati" in cui è possibile ottenere i risultati e chiamare metodi su tali risultati. L'esempio comune è recuperare un oggetto utente da DBIC e quindi chiamare un metodo per verificare se la loro password è valida.

La distribuzione dello schema può essere difficile, ma è sempre difficile. DBIC ha strumenti (alcuni in moduli esterni) che lo rendono più semplice e probabilmente più semplice della gestione manuale dei propri schemi.

Dall'altro lato della medaglia, ci sono altri strumenti che fanno appello ad altre sensibilità, come gli involucri DBI al gusto mojo. Questi hanno il fascino di essere magri e tuttavia ancora utilizzabili. La maggior parte ha anche preso spunto da Mojo :: Pg (l'originale) e ha aggiunto funzionalità utili come la gestione degli schemi nell'integrazione di file flat e pubsub.

Questi moduli aromatizzati Mojo sono nati da un altro punto debole di DBIC, ovvero che non è (ancora) in grado di eseguire query asincrone. Gli autori mi hanno assicurato che è tecnicamente possibile forse anche rapidamente, ma ci sono problemi nel progettare un API che sarebbe appropriato. (Devo ammettere che mi è stato persino chiesto di aiutare con questo, e mentre lo farei, non so come muovere l'ago nel tempo che devo dedicare ad esso).

TL; DR usano DBIC a meno che non ami SQL o non hai bisogno di asincrono, nel qual caso studia i wrapper DBI al gusto Mojo.


6

Ho scritto i miei pensieri su questo in DBIC vs DBI tre anni fa. Per riassumere, ho elencato due motivi principali:

  1. DBIC significa che non devo pensare a tutto il banale SQL necessario praticamente per qualsiasi applicazione che scrivo.
  2. DBIC mi restituisce oggetti dal database piuttosto che strutture di dati stupide. Ciò significa che ho tutta la qualità OO standard con cui giocare. In particolare trovo davvero utile poter aggiungere metodi ai miei oggetti DBIC.

Per quanto riguarda gli anti-pattern, anche l'unico a cui riesco a pensare è la performance. Se vuoi davvero spremere ogni clock dalla tua CPU, forse DBIC non è lo strumento giusto per il lavoro. Ma, certamente per le applicazioni che scrivono, quei casi sono sempre più rari. Non ricordo l'ultima volta che ho scritto una nuova applicazione che parlava con un database e non utilizzava DBIC. Naturalmente, aiuta se si conosce un po 'su come ottimizzare le query generate da DBIC.


2
Eh, non posso correggere gli errori di battitura perché non cambio abbastanza personaggi ("rto ttool"). Curiosamente. Ma questo è il tipo di risposta che mi confonde. Penso che nel tuo post su PerlHacks, tu affronti una cosa che Rob sottolinea, ma non considerare l'altra. In molti casi, ho trovato persone che tornavano al manuale SQL.
brian d foy,

1

Il modo in cui lo faccio ridimensionare:

  1. creare una classe che fornisce il costruttore del socket DBI e i metodi di test.

  2. derivare quella classe nelle classi di query SQL (una classe per tabella sql) e testare il socket al momento del costruttore.

  3. utilizzare le variabili con ambito di classe per contenere il nome della tabella e i nomi delle colonne dell'indice primario.

  4. Scrivi tutto il nome della tabella di interpolazione SQL e la colonna dell'indice primario da quelle variabili invece di definirle staticamente in SQL.

  5. utilizzare le macro dell'editor per consentire di creare coppie di metodi DBI di base (preparare ed eseguire) mentre si digita SOLO l'istruzione sql.

Se riesci a farlo, puoi scrivere il codice API pulito sopra il DBI tutto il giorno con relativa facilità.

Quello che troverai è che molte delle tue query saranno portatili su più tabelle. A quel punto puoi tagliare e incollare in una classe EXPORTER e ricoprirli dove necessario. Qui entra in gioco l'interpolazione nell'ambito della classe del nome della tabella e dei nomi delle colonne dell'indice primario.

Ho usato questo approccio per scalare a centinaia di metodi DBI con prestazioni relativamente buone. Non vorrei provare a mantenere il codice DBI in nessun altro modo.

Quando utilizzare il DBI: sempre.

Non penso che sia stata la tua vera domanda. La tua vera domanda era: "Sembra un enorme PITA, per favore dimmi che non devo farlo?"

Non Disporlo correttamente e la parte DBI diventa abbastanza ridondante da essere in grado di automatizzarla maggiormente.


Qualche possibilità che tu abbia un progetto open source che potresti condividere, o forse anche solo un esempio su Github con un esempio di ogni classe? Penso che le idee che stai dicendo siano interessanti e probabilmente sarebbero realizzabili per i progetti di molte persone, ma sarebbe un po 'più facile andare avanti con alcuni esempi.
msouth,

0

Non sono un esperto del Perl, ma lo uso molto. Ci sono molte cose che non conosco o che so fare meglio; alcune cose che non sono ancora in grado di capire, nonostante la documentazione.

Tendo a iniziare con DBI perché penso "Oh, questo è un progetto semplice, non ho bisogno del gonfio di un ORM e non voglio seccature con i moduli di installazione e Schema". Ma molto rapidamente - quasi ogni volta - inizio rapidamente a maledirmi per quella decisione. Quando voglio iniziare a diventare creativo nelle mie query SQL (query dinamiche, non solo comparare i segnaposto), faccio fatica a mantenere la sanità mentale usando DBI. SQL :: Abstract aiuta molto, e in genere è probabilmente sufficiente per me. Ma la mia prossima lotta mentale è mantenere così tanto SQL all'interno del mio codice. È molto fastidioso per me avere linee e linee di SQL incorporato all'interno di brutti heredocs. Forse devo usare un IDE di qualità.

Alla fine, il più delle volte, mi attengo al DBI dritto. Ma continuo a desiderare che ci sia un modo migliore. DBIx :: Class ha alcune caratteristiche davvero pulite e l'ho usato alcune volte, ma sembra così esagerato per tutti tranne i progetti più grandi. Non sono nemmeno sicuro di trovare più ingombrante da gestire: DBI con SQL sparsi o DBIC con moduli Schema sparsi.

(Oh, cose come vincoli e trigger è un grande vantaggio per me per DBIC.)


4
Questa risposta non arriva molto bene al punto: certo, hai problemi quando usi DBIx, ma perché hai questi problemi? DBI non è flessibile, accoppiato troppo strettamente con il DB, non è scalabile o cosa?
Jay Elston,
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.