Я задал этот вопрос в Твиттере также #clojure канал IRC, все же не получил ответов.
Было несколько статей о Clojure-for-Ruby-programmers, Clojure-for-lisp-programmers.. но то, что является недостающей частью, является Clojure для программистов ActiveRecord.
Были статьи о взаимодействии с MongoDB, Redis, и т.д. - но это хранилища значения ключа в конце дня. Однако происходя из среды направляющих, мы привыкли думать о базах данных с точки зрения наследования - has_many, полиморфный, belongs_to, и т.д.
Несколько статей о MySQL Clojure/Compojure + (ffclassic) - копаются прямо в sql. Конечно, могло бы случиться так, что ORM вызывает несоответствие импеданса, но факт остается, что после размышления как ActiveRecord, очень трудно думать любой другой путь.
Я полагаю, что реляционный DBS, предоставьте себя очень хорошо объектно-ориентированной парадигме из-за них быть, по существу, Наборы. Материал как activerecord очень хорошо подходит для моделирования этих данных. Для, например, блог - просто помещенный
class Post < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
end
Как делает одну модель это в Clojure - который является так строго анти-OO? Возможно, вопрос был бы лучше, если он упомянул все языки функционального программирования, но мне больше интересно с точки зрения Clojure (и примеры Clojure)
В настоящее время в разработке находится пара ORM-подобных библиотек.
В списке рассылки некоторые (умные) люди недавно описали некоторые другие модели того, как это может работать . Каждая из этих библиотек использует совершенно другой подход к проблеме, поэтому обязательно изучите их все.
Вот расширенный пример , использующий, например, Oyako. Эта библиотека не готова к производству и все еще находится в стадии интенсивной разработки, поэтому пример может стать недействительным через неделю, но он приближается. В любом случае это доказательство концепции. Дайте ему немного времени, и у кого-нибудь получится хорошая библиотека.
Обратите внимание, что clojure.contrib.sql
уже позволяет вам извлекать записи из БД (через JDBC) и в итоге получить неизменяемые хеш-карты, представляющие записи. Поскольку данные попадают в карты нормалей, все бесчисленные основные функции Clojure, которые работают с картами, уже работают с этими данными.
Что еще предлагает ActiveRecord? Я могу думать о двух вещах.
Я мысленно моделирую это: сначала вы определяете отношения между таблицами. Для этого не нужны мутации или объекты. Это статичное описание.AR распределяет эту информацию по группе классов, но я рассматриваю ее как отдельную (статическую) сущность.
Используя определенные отношения, вы можете очень кратко писать запросы. Например, с Oyako:
(def my-data (make-datamap db [:foo [has-one :bar]]
[:bar [belongs-to :foo]]))
(with-datamap my-data (fetch-all :foo includes :bar))
Тогда у вас будет несколько объектов foo
, каждый из которых имеет ключ : bar
, в котором перечислены ваши столбцы.
В Ояко «карта данных» - это просто карта. Сам запрос - это карта. Возвращенные данные - это вектор карт (векторов карт). Таким образом, вы получаете стандартный и простой способ конструировать, манипулировать и перебирать все эти вещи, что приятно. Добавьте немного сахара (макросы и обычные функции), чтобы вы могли кратко создавать и легко управлять этими картами, и это в конечном итоге окажется довольно мощным. Это только один способ, есть много подходов.
Если вы посмотрите на библиотеку вроде Sequel в качестве другого примера, у вас будут такие вещи, как:
Artist.order(:name).last
Но почему эти функции должны быть методами, которые живут внутри объектов? Эквивалент в Oyako может быть таким:
(last (-> (query :artist)
(order :name)))
Опять же, зачем для этого нужны объекты в стиле объектно-ориентированных приложений, мутации или наследование реализации? Сначала выберите запись (как неизменяемую карту), затем протяните ее через кучу функций, связывая
с ней новые значения по мере необходимости, затем вставьте ее обратно в базу данных или удалите, вызвав функцию на Это.
Умная библиотека могла бы использовать метаданные, чтобы отслеживать, какие поля были изменены, чтобы уменьшить количество запросов, необходимых для выполнения обновлений. Или пометить запись, чтобы функции БД знали, в какую таблицу вставить ее обратно.Я думаю, Carte даже выполняет каскадные обновления (обновляя подзаписи при изменении родительской записи).
Я считаю, что многое из этого принадлежит базе данных, а не библиотеке ORM. Например, каскадное удаление (удаление дочерних записей при удалении родительских записей): у AR есть способ сделать это, но вы можете просто добавить предложение в таблицу в БД, а затем позволить своей БД обработать это и больше никогда не беспокоиться. То же самое со многими видами ограничений и проверок.
Но если вам нужны перехватчики, их можно реализовать очень легким способом, используя простые старые функции или мультиметоды. Раньше у меня была библиотека базы данных, которая вызывала ловушки в разное время цикла CRUD, например после сохранения
или до удаления
. Это были простые методы диспетчеризации по именам таблиц. Это позволяет вам по своему усмотрению расширять их до ваших собственных таблиц.
(defmulti before-delete (fn [x] (table-for x)))
(defmethod before-delete :default [& _]) ;; do nothing
(defn delete [x] (when (before-delete x) (db-delete! x) (after-delete x)))
Позже, как конечный пользователь, я мог написать:
(defmethod before-delete ::my_table [x]
(if (= (:id x) 1)
(throw (Exception. "OH NO! ABORT!"))
x))
Легко и расширяемо, и это заняло пару секунд. Никакого OO не видно. Может быть, не так изощренно, как AR, но иногда достаточно простого.
Посмотрите на эту библиотеку , чтобы увидеть еще один пример определения ловушек.
Это есть в Карт. Я не особо задумывался о них, но создание версий базы данных и добавление в нее данных, похоже, не выходит за рамки возможностей Clojure.
Много пользы от AR исходит из всех соглашений для именования таблиц и столбцов, а также всех вспомогательных функций для написания слов с заглавной буквы, форматирования дат и т. Д. Это не имеет ничего общего с OO vs.не-ОО; AR просто доработана, потому что на нее было потрачено много времени. Возможно, в Clojure еще нет библиотеки AR-класса для работы с данными БД, но дайте ей время.
Вместо того, чтобы иметь объект, который знает, как уничтожить себя, изменить себя, сохранить себя, связать себя с другими данными, извлечь себя и т. Д., Вместо этого у вас есть данные, которые являются просто данными, а затем вы определяете функции, которые работают с этим данные: сохраняет, уничтожает, обновляет в БД, извлекает, связывает с другими данными. Так Clojure работает с данными в целом, и данные из базы данных ничем не отличаются.
Foo.find(1).update_attributes(:bar => "quux").save!
=> (with-db (-> (fetch-one :foo :where {:id 1})
(assoc :bar "quux")
(save!)))
Foo.create!(:id => 1)
=> (with-db (save (in-table :foo {:id 1})))
Что-то вроде того. Это наизнанку по сравнению с тем, как работают объекты, но обеспечивает ту же функциональность. Но в Clojure вы также получаете все преимущества написания кода в стиле FP.