Функциональное программирование базы данных в Clojure - PullRequest
9 голосов
/ 05 января 2011

«Заманчиво, если единственный инструмент, который у тебя есть - молоток, обращаться со всем, как с гвоздем». - Авраам Маслоу

Мне нужно написать инструмент для выгрузки большой иерархической (SQL) базы данных в XML. Иерархия состоит из таблицы Person с вспомогательными таблицами Address, Phone и т. Д.

  • Мне нужно сбросить тысячи строк, поэтому я хотел бы делать это постепенно и не сохранять весь файл XML в памяти.

  • Я хотел бы выделить не чистый функциональный код для небольшой части приложения.

  • Я думаю, что это может быть хорошей возможностью для изучения FP и параллелизма в Clojure. Я также могу показать преимущества использования неизменных данных и многоядерности моим скептически настроенным коллегам.

Я не уверен, какой должна быть общая архитектура приложения. Я думаю, что могу использовать нечистую функцию для извлечения строк базы данных и возврата отложенной последовательности, которая затем может быть обработана чистой функцией, которая возвращает фрагмент XML.

Для каждой строки Person я могу создать Future и обработать несколько параллельно (порядок вывода не имеет значения).

При обработке каждого Person задача извлекает соответствующие строки из таблиц Address, Phone и т. Д. И генерирует вложенный XML.

Я могу использовать универсальную функцию для обработки большинства таблиц, опираясь на метаданные базы данных для получения информации о столбцах, со специальными функциями для нескольких таблиц, которые нуждаются в пользовательской обработке. Эти функции могут быть перечислены в map(table name -> function).

Правильно ли я поступаю? Я легко могу вернуться к выполнению этого в ОО с использованием Java, но это было бы неинтересно.

Кстати, есть ли хорошие книги по шаблонам FP или архитектуре? У меня есть несколько хороших книг по Clojure, Scala и F #, но хотя каждая из них хорошо описывает язык, ни одна из них не смотрит на «общую картину» разработки функций программирования.

1 Ответ

6 голосов
/ 05 января 2011

Хорошо, круто, вы используете это как возможность продемонстрировать Clojure.Итак, вы хотите продемонстрировать FP и параллелизм.Подумайте об этом.

Чтобы удивить ваших собеседников, я хотел бы продемонстрировать:

  • Производительность вашей программы с использованием одного потока.
  • Как увеличивается производительность вашей программыпо мере увеличения количества потоков.
  • Как легко перевести вашу программу из однопоточной в многопоточную.

Вы можете создать функцию для выгрузки одной таблицы вXML-файл.

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

С этим вы можете решить весь или свой код для основной задачи преобразования ваших реляционных данных в XML.

Теперь, когда вы решили основную проблему, посмотритеесли добавление большего количества потоков увеличит вашу скорость.

Вы можете изменить table-to-xml, чтобы принять дополнительный параметр:

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

Это означает, что у вас есть n потоков, работающих на одной таблице.В этом случае каждый поток может обрабатывать каждую n-ю строку.Проблема размещения нескольких потоков в одной таблице состоит в том, что каждый поток захочет записать в один и тот же XML-файл.Это узкое место может сделать стратегию бесполезной, но ее стоит попробовать.

Если приемлемо создание одного XML-файла на таблицу, то порождение одного потока на таблицу может быть легким выигрышем.

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

Используя только взаимно-однозначные отношения между таблицами, файлами и потоками: в качестве руководства, я бы ожидал, что ваш код не будет содержать никаких ссылок или досинксов, и решение должно быть довольно простым.

Как только выначните создавать несколько потоков для каждой таблицы, к которой вы добавляете сложность и, возможно, не увидите значительного увеличения производительности.

В любом случае вам, вероятно, потребуется один или два запроса на таблицу для получения значений и метаданных.Относительно вашего комментария о нежелании загружать все данные в память: каждый поток будет обрабатывать только одну строку за раз.

Надеюсь, это поможет!

Учитывая ваш комментарий, вот некоторые псевдо-ишкод, который может помочь:

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

Вы можете использовать библиотеку исполнителей Java для создания пула потоков.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...