ORM para clojure?
clojureql (8)
¿Has visitado la biblioteca de Korma http://sqlkorma.com/ ? Le permite definir relaciones de tabla y abstracción sobre uniones. Creo que una de las principales razones por las que no hay ningún ORM para clojure es porque van en contra de las ideas de simplicidad de Rich Hickey sobre las que se fundó el lenguaje. Mira esta charla: http://www.infoq.com/presentations/Simple-Made-Easy
Estaba leyendo este sitio sobre la pila web de clojure:
http://brehaut.net/blog/2011/ring_introduction
y tiene esto que decir sobre ORM para clojure:
"No hay ORM SQL / Relational DB para Clojure por razones obvias".
La razón obvia que puedo ver es que la asignación a objetos ocurre automáticamente cuando se realiza una consulta clojure.contrib.sql o clojureql. Sin embargo, parece que se necesita un trabajo adicional para hacer relaciones de uno a muchos o de muchos a muchos (aunque quizás no demasiado).
Encontré este escrito para uno-a-muchos: http://briancarper.net/blog/493/
Con lo que no estoy seguro estoy de acuerdo; parece suponer que ambas tablas se extraen de la base de datos y luego la tabla unida se filtra en la memoria. En la práctica, creo que la consulta sql especificaría los criterios where.
Entonces me pregunto, ¿hay alguna forma bastante obvia de hacer automáticamente relaciones de uno a muchos a través de clojureql o clojure.contrib.sql? Lo único que se me ocurre es algo como esto (usando el típico post de blog / comentario):
(defn post [id]
@(-> (table :posts)
(select (where :id id))))
(defn comments [post_id]
@(-> (table :comments)
(select (where :post_id post_id))))
(defn post-and-comments [id]
(assoc (post id) :comments (comments id)))
¿Hay alguna forma de automatizar este concepto o es tan bueno como es posible?
A riesgo de nadar en las aguas con algunos de los bateadores pesados SO (para mezclar mis metáforas a fondo;) - seguramente una de las mejores características de ORM es que, en la gran mayoría de los casos, el programador pragmático nunca debe usar o incluso pensar en SQL. En el peor de los casos, puede ser necesaria una programación hacky con los resultados de un par de consultas, sobre la base de que esto se convertirá en SQL sin formato cuando esa optimización sea necesaria, por supuesto,;).
Decir que ORM no es necesario por la razón "obvia", de alguna manera no tiene sentido. Además de comenzar a usar una DSL para modelar SQL está agravando este error. En la gran mayoría de los marcos web, el modelo de objetos es una DSL utilizada para describir los datos almacenados por la aplicación web, y SQL simplemente el lenguaje declarativo requerido para comunicar esto a la base de datos.
Orden de pasos al usar ROR, django o Spring:
- Describe tus modelos en un formato OOP
- Abra REPL y haga algunos ejemplos de modelos
- Construye algunas vistas
- Verificar los resultados en el navegador web
Ok, entonces podrías usar un orden ligeramente diferente, pero con suerte entiendes el punto. Pensar en SQL o una DSL que lo describe es un largo camino en la lista. En cambio, la capa del modelo elimina todo el SQL, lo que nos permite crear objetos de datos que modelan de cerca los datos que deseamos utilizar en el sitio web.
Estoy totalmente de acuerdo en que OOP no es una bala de plata, sin embargo, los datos de modelado en un marco web es algo que definitivamente es bueno, y la explotación de la capacidad de Clojure para definir y manipular las clases de Java parece ser una buena combinación aquí.
Los ejemplos en la pregunta demuestran claramente cuán doloroso puede ser SQL, y las DSL como Korma son solo una solución parcial: "Supongamos que tenemos algunas tablas en una base de datos ..." - er, pensé que mi DSL iba a crear esas ¿para mi? ¿O es esto algo que hace mejor un lenguaje OOP? ;)
Hice esta pregunta hace bastante tiempo, pero me encontré con lo siguiente y decidí agregarlo como respuesta en caso de que alguien esté interesado:
La biblioteca conocida como aggregate puede encargarse de la mayoría de los problemas que tiene aquí. No es un ORM a escala completa, pero si le indica el gráfico de relación para su esquema de base de datos, entonces proporciona implementaciones CRUD que automáticamente recorren el gráfico de relaciones. Es útil si ya está usando algo como Yesql o consultas SQL sin procesar, ya que se conecta fácilmente a una implementación que usa mapas de resultados simples.
La razón "obvia" por la que no necesita ORM como tal en Clojure es porque Clojure idiomático no tiene objetos per se.
La mejor manera de representar los datos en un programa Clojure es como elementos de datos simples de strutures de datos simples (mapas y vectores). Asignar estos a las filas SQL es mucho menos complejo y tiene una falta de compatibilidad de impedancia mucho menor que el ORM completo.
Además, con respecto a la parte de su pregunta relacionada con la formación de una consulta SQL compleja ... leer su código, realmente no tiene ninguna ventaja clara sobre SQL. ¡No tengas miedo de SQL! Es genial para lo que hace: manipulación de datos relacionales.
ORM es una optimización prematura. Walkable es la nueva biblioteca sql para Clojure que tiene un enfoque holístico. Compruébelo aquí https://github.com/walkable-server/walkable .
Un ejemplo del mundo real para los escépticos de readmes elegantes: https://github.com/walkable-server/realworld
Todavía no hay una biblioteca de alto nivel para crear consultas relacionales complejas que yo sepa. Hay muchas maneras de abordar este problema (el enlace que proporcionó es de una sola manera), pero incluso si ClojureQL proporciona una DSL realmente agradable sobre la que pueda basarse, aún omite algunas características importantes. Aquí hay un ejemplo rápido y sucio de una macro que genera uniones imbricadas:
(defn parent-id [parent]
(let [id (str (apply str (butlast (name parent))) "_id")]
(keyword (str (name parent) "." id))))
(defn child-id [parent child]
(let [parent (apply str (butlast (name parent)))]
(keyword (str (name child) "." parent "_id"))))
(defn join-on [query parent child]
`(join ~(or query `(table ~parent)) (table ~child)
(where
(~''= ~(parent-id parent)
~(child-id parent child)))))
(defn zip [a b] (map #(vector %1 %2) a b))
(defmacro include [parent & tables]
(let [pairs (zip (conj tables parent) tables)]
(reduce (fn [query [parent child]] (join-on query parent child)) nil pairs)))
Con esto puedes hacer (include :users :posts :comments)
y sacar este SQL de él:
SELECT users.*,posts.*,comments.*
FROM users
JOIN posts ON (users.user_id = posts.user_id)
JOIN comments ON (posts.post_id = comments.post_id)
Sin embargo, hay un problema importante con esta técnica. El principal problema es que las columnas devueltas para todas las tablas se agruparán en el mismo mapa. Como los nombres de columna no se pueden calificar automáticamente, no funcionará si hay una columna con un nombre similar en tablas diferentes. Esto también le impedirá agrupar los resultados sin tener acceso al esquema. No creo que exista una forma de no saber el esquema de la base de datos para este tipo de cosas, así que todavía hay mucho trabajo por hacer. Creo que ClojureQL siempre será una biblioteca de bajo nivel, por lo que deberá esperar a que exista alguna otra biblioteca de nivel superior o crear la suya propia.
Para crear dicha biblioteca, siempre puede echar un vistazo a la clase DatabaseMetaData de JDBC para proporcionar información sobre el esquema de la base de datos. Todavía estoy trabajando en un analizador de bases de datos para Lobos que lo usa (y algunas cosas personalizadas) pero aún estoy lejos de empezar a trabajar en consultas SQL, que podría agregar en la versión 2.0.