2011-01-05 13 views
9

"È allettante, se l'unico strumento che hai è un martello, trattare tutto come se fosse un chiodo." - Abraham MaslowProgrammazione funzionale del database in Clojure

Ho bisogno di scrivere uno strumento per scaricare un grande database gerarchico (SQL) in XML. La gerarchia consiste in una tabella Person con le tabelle Address, Phone, ecc.

  • devo scaricare migliaia di righe, quindi mi piacerebbe farlo in modo incrementale e non mantenere l'intero file XML in memoria.

  • Vorrei isolare un codice funzione non puro in una piccola parte dell'applicazione.

  • Sto pensando che questa potrebbe essere una buona opportunità per esplorare FP e la concorrenza in Clojure. Posso anche mostrare i benefici dei dati immutabili e l'utilizzo multi-core ai miei colleghi scettici.

io non sono sicuro di come l'architettura complessiva della domanda deve essere. Sto pensando che posso usare una funzione impura per recuperare le righe del database e restituire una sequenza lazy che può essere elaborata da una funzione pura che restituisce un frammento XML.

Per ogni riga Person, è possibile creare un Future e diversi elaborati in parallelo (l'ordine di output non è rilevante).

Mentre ogni Person viene elaborato, l'attività recupera le righe appropriate dalle tabelle Address, Phone e così via e genera l'XML nidificato.

È possibile utilizzare una funzione generica per elaborare la maggior parte delle tabelle, basandosi sui metadati del database per ottenere le informazioni sulla colonna, con funzioni speciali per le poche tabelle che richiedono un'elaborazione personalizzata. Queste funzioni potrebbero essere elencate in un map(table name -> function).

Sto andando su questo nel modo giusto? Posso facilmente tornare a farlo in OO usando Java, ma non sarebbe divertente.

BTW, ci sono buoni libri su modelli FP o architettura? Ho molti buoni libri su Clojure, Scala e F #, ma sebbene ciascuno tratti bene il linguaggio, nessuno guarda al "quadro generale" del design della programmazione di funzioni.

+3

A mia conoscenza non esiste un libro "FP for architects". Tuttavia, se leggi "Strutture dati puramente funzionali" end to end, avrai sicuramente un'idea migliore di come applicare i concetti FP nel mondo reale. Vedi http://www.amazon.com/Purely-Functional-Structures-Chris-Okasaki/dp/0521663504 –

+0

@ Chris Smith: Ho quello sulla mia lista dei desideri Amazon. Lo guarderò. – Ralph

risposta

6

Ok, bello, stai usando questa come un'opportunità per mostrare Clojure. Quindi, vuoi dimostrare FP e concorrenza. Ricevuto.

Per wow i vostri interlocutori vorrei fare un punto per dimostrare:

  • prestazioni del vostro programma con un singolo thread.
  • Le prestazioni del tuo programma aumentano all'aumentare del numero di thread.
  • Quanto è semplice portare il programma da singolo a multi-thread.

È possibile creare una funzione per scaricare una singola tabella in un file XML.

(defn table-to-xml [name] ...) 

Con che si può lavorare fuori tutto o il vostro codice per il compito principale di convertire i dati relazionali in XML.

Ora che hai risolto il problema principale, vedi se lanciare più fili aumenta la velocità.

Si potrebbe modificare table-to-xml di accettare un parametro aggiuntivo:

(defn table-to-xml [name thread-count] ...) 

Questo implica che si dispone di n discussioni che lavorano su un tavolo. In questo caso ogni thread potrebbe elaborare ogni ennesima riga. Un problema con l'inserimento di più thread su una tabella è che ogni thread vorrà scrivere nello stesso file XML. Questo collo di bottiglia può rendere la strategia inutile, ma vale la pena sparare.

Se la creazione di un file XML per tabella è accettabile, la generazione di un thread per tabella sarà probabilmente una facile vincita.

(map #(future (table-to-xml %)) (table-names)) 

Utilizzando solo una relazione uno-a-uno tra tavoli, i file e le discussioni: come linea guida, mi aspetterei il codice per non contiene alcun arbitri o dosyncs e la soluzione dovrebbe essere abbastanza semplice.

Una volta avviata la generazione di più thread per tabella, si aggiunge complessità e si potrebbe non notare un notevole aumento delle prestazioni.

In ogni caso è probabile che ci siano una o due query per tabella per ottenere valori e meta-dati. Per quanto riguarda il tuo commento su non voler caricare tutti i dati in memoria: ogni thread elaborerebbe solo una riga alla volta.

Spero che questo aiuti!

Dato il tuo commento qui qualche pseudo codice-ish che potrebbe aiutare:

(defn write-to-xml [person] 
    (dosync 
    (with-out-append-writer *path* 
    (print-person-as-xml)))) 

(defn resolve-relation [person table-name one-or-many] 
    (let [result (query table-name (:id person))] 
    (assoc person table-name (if (= :many one-or-many) 
           result 
           (first result))))) 

(defn person-to-xml [person] 
    (write-to-xml 
    (-> person 
     (resolve-relation "phones" :many) 
     (resolve-relation "addresses" :many)))) 

(defn get-people [] 
    (map convert-to-map (query-db ...))) 

(defn people-to-xml [] 
    (map (fn [person] 
     (future (person-to-xml %))) 
     (get-people))) 

Si potrebbe considerare l'utilizzo di esecutori Libreria Java per creare un pool di thread.

+0

Stavo pensando di emettere l'elemento radice dell'XML ('persone'), quindi di interrogare il database per tutte le righe di persona e avviare un' Futuro' separato per ogni riga. Ogni 'Future' sarebbe responsabile per interrogare le altre tabelle e generare gli elementi XML nidificati (' address', 'phones', ecc.) E infine restituire il frammento' person' completo. Il problema più grande che ho è come mantenere la maggior parte delle funzioni "pure". L'utilizzo di funzioni di ordine superiore potrebbe consentirmi di eseguire il FP equivalente a "Inversion-of-Control". – Ralph

+0

Gotcha. Aggiornerò la mia risposta per dare ulteriori suggerimenti. – Psyllo

Problemi correlati