2011-01-05 15 views
9

"Es tentador, si la única herramienta que tiene es un martillo, tratar todo como si fuera un clavo". - Abraham MaslowProgramación funcional de la base de datos en Clojure

Necesito escribir una herramienta para volcar una gran base de datos jerárquica (SQL) a XML. La jerarquía consiste en una tabla Person con las tablas subsidiarias Address, Phone, etc.

  • que tienen que volcar miles de filas, por lo que me gustaría hacerlo de forma incremental y no mantener a todo el archivo XML en la memoria.

  • Me gustaría aislar el código de función no pura en una pequeña parte de la aplicación.

  • Estoy pensando que esta podría ser una buena oportunidad para explorar FP y concurrencia en Clojure. También puedo mostrar los beneficios de la utilización de datos inmutables y multi-core a mis colegas escépticos.

No estoy seguro de cómo debería ser la arquitectura general de la aplicación. Estoy pensando que puedo usar una función impura para recuperar las filas de la base de datos y devolver una secuencia diferida que luego puede ser procesada por una función pura que devuelve un fragmento XML.

Para cada fila Person, puedo crear un Future y tener varios procesados ​​en paralelo (el orden de salida no importa).

A medida que cada Person se procesa, la tarea va a recuperar las filas correspondientes de la Address, Phone, etc. tablas y generar el XML anidada.

Puedo utilizar una función genérica para procesar la mayoría de las tablas, confiando en los metadatos de la base de datos para obtener la información de la columna, con funciones especiales para las pocas tablas que necesitan un procesamiento personalizado. Estas funciones se pueden enumerar en un map(table name -> function).

¿Estoy hablando de esto de la manera correcta? Puedo recurrir fácilmente a hacerlo en OO usando Java, pero eso no sería divertido.

BTW, ¿hay buenos libros sobre patrones FP o arquitectura? Tengo varios buenos libros sobre Clojure, Scala y F #, pero aunque cada uno cubre bien el lenguaje, ninguno mira la "gran imagen" del diseño de programación de funciones.

+3

Que yo sepa, no existe un libro "FP para arquitectos". Sin embargo, si lee "Estructuras de datos puramente funcionales" de principio a fin, definitivamente tendrá una mejor idea de cómo aplicar los conceptos de PF en el mundo real. Ver http://www.amazon.com/Purely-Functional-Structures-Chris-Okasaki/dp/0521663504 –

+0

@Chris Smith: Tengo esa en mi lista de deseos de Amazon. Lo comprobaré. – Ralph

Respuesta

6

Bien, bien, estás usando esto como una oportunidad para mostrar Clojure. Entonces, quieres demostrar FP y concurrencia. Entendido.

Para impresionar a sus interlocutores que haría un punto para demostrar:

  • rendimiento de su programa usando un solo hilo.
  • Cómo aumenta el rendimiento de su programa a medida que aumenta el número de subprocesos.
  • Qué fácil es llevar su programa de simple a múltiple.

Puede crear una función para volcar una sola tabla en un archivo XML.

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

Con eso usted puede resolver todo o su código para la tarea principal de convertir sus datos relacionales a XML.

Ahora que ha resuelto el problema central, vea si lanzar más hilos aumentará su velocidad.

Es posible modificar table-to-xml para aceptar un parámetro adicional:

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

Esto implica que tiene n hilos que trabajan en una misma mesa. En este caso, cada hilo podría procesar cada enésima fila. Un problema al colocar varios subprocesos en una tabla es que cada subproceso va a querer escribir en el mismo archivo XML. Ese cuello de botella puede hacer que la estrategia sea inútil, pero vale la pena intentarlo.

Si la creación de un archivo XML por tabla es aceptable, engendrar un hilo por tabla probablemente sea una ganancia fácil.

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

Utilizando sólo una relación de uno a uno entre las mesas, archivos e hilos: como guía, yo esperaría que su código no contiene ningún árbitros o dosyncs y la solución debería ser bastante sencillo.

Una vez que comience a generar múltiples subprocesos por tabla, está agregando complejidad y es posible que no aumente mucho el rendimiento.

En cualquier caso, es probable que tenga una o dos consultas por tabla para obtener valores y metadatos. En cuanto a su comentario sobre no querer cargar todos los datos en la memoria: cada hilo solo estaría procesando una fila a la vez.

Espero que ayude!

Dada su comentario aquí hay un código de pseudo-ish que podría ayudar:

(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))) 

Se podría considerar el uso de los ejecutores de Java biblioteca para crear un grupo de subprocesos.

+0

Estaba pensando en emitir el elemento raíz del XML ('personas'), luego consultar la base de datos para todas las filas de personas y comenzar un' Futuro' separado para cada fila. Cada 'Futuro' sería responsable de consultar las otras tablas y generar los elementos XML anidados (' direcciones', 'teléfonos', etc.) y finalmente devolver el fragmento completo' persona'. El problema más grande que tengo es cómo mantener la mayoría de las funciones "puras". Usar funciones de orden superior me puede permitir hacer el FP equivalente a "Inversión de control". – Ralph

+0

Gotcha. Actualizaré mi respuesta para dar algunas sugerencias más. – Psyllo

Cuestiones relacionadas