“如果您拥有的唯一工具是锤子,那么将一切都当作钉子来对待是很诱人的。” - Abraham Maslow
我需要编写一个工具来将大型分层 (SQL) 数据库转储为 XML。该层次结构由一个 Person
表及其附属的 Address
、Phone
等表组成。
我必须转储数千 行,所以我想逐步转储,而不是将整个 XML 文件保存在内存中。
我想将非纯函数代码隔离到应用程序的一小部分。
我认为这可能是探索 Clojure 中的 FP 和并发的好机会。我还可以向持怀疑态度的同事展示不可变数据和多核利用的好处。
我不确定应用程序的整体架构应该如何。我在想我可以使用一个非纯函数来检索数据库行并返回一个惰性序列,然后可以由一个返回 XML 片段的纯函数进行处理。
对于每个 Person
行,我可以创建一个 Future
并并行处理多个(输出顺序无关紧要)。
在处理每个 Person
时,任务将从 Address
、Phone
等表中检索适当的行,并生成嵌套的XML。
我可以使用一个通用函数来处理大部分表,依靠数据库元数据来获取列信息,对少数需要自定义处理的表使用特殊函数。这些函数可以列在 map(table name -> function)
中。
我的处理方式是否正确?我可以很容易地退回到使用 Java 在 OO 中执行此操作,但这并不有趣。
顺便说一句,有没有关于 FP 模式或架构的好书?我有几本关于 Clojure、Scala 和 F# 的好书,但尽管每本都很好地涵盖了这门语言,但没有一本着眼于函数编程设计的“大局”。
最佳答案
好的,很酷,您可以借此机会展示 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 执行程序库来创建线程池。
关于Clojure 中的数据库函数式编程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4604149/