clojure 中的关系数据库中的一个_model_数据是如何实现的?

发布于 2024-09-06 02:55:20 字数 875 浏览 13 评论 0原文

我在 Twitter 以及 #clojure IRC 频道上提出了这个问题,但没有得到回复。

已经有几篇关于 Clojure-for-Ruby-programmers、Clojure-for-lisp-programmers.. 但缺少的部分是 Clojure for ActiveRecord 程序员

有一些关于与 MongoDB、Redis 等交互的文章 - 但这些最终都是键值存储。然而,来自 Rails 背景的我们习惯于从继承的角度思考数据库 - has_many、多态、belongs_to 等。

关于 Clojure/Compojure + MySQL 的几篇文章 (ffclassic) - 深入研究 sql。当然,ORM 可能会导致阻抗不匹配,但事实是,在像 ActiveRecord 一样思考之后,很难再以任何其他方式思考。

我相信关系数据库非常适合面向对象范例,因为它们本质上是集合。像 activerecord 这样的东西非常适合对这些数据进行建模。 例如,一个博客 - 简单地说

class Post < ActiveRecord::Base
  has_many :comments
 end


 class Comment < ActiveRecord::Base
   belongs_to :post
 end

如何在 Clojure 中对此进行建模 - 这是严格反 OO 的?如果它涉及所有函数式编程语言,也许这个问题会更好,但从 Clojure 的角度(以及 Clojure 示例)我更感兴趣

I have asked this question on twitter as well the #clojure IRC channel, yet got no responses.

There have been several articles about Clojure-for-Ruby-programmers, Clojure-for-lisp-programmers.. but what is the missing part is Clojure for ActiveRecord programmers .

There have been articles about interacting with MongoDB, Redis, etc. - but these are key value stores at the end of the day. However, coming from a Rails background, we are used to thinking about databases in terms of inheritance - has_many, polymorphic, belongs_to, etc.

The few articles about Clojure/Compojure + MySQL (ffclassic) - delve right into sql. Of course, it might be that an ORM induces impedence mismatch, but the fact remains that after thinking like ActiveRecord, it is very difficult to think any other way.

I believe that relational DBs, lend themselves very well to the object-oriented paradigm because of them being , essentially, Sets. Stuff like activerecord is very well suited for modelling this data.
For e.g. a blog - simply put

class Post < ActiveRecord::Base
  has_many :comments
 end


 class Comment < ActiveRecord::Base
   belongs_to :post
 end

How does one model this in Clojure - which is so strictly anti-OO ? Perhaps the question would have been better if it referred to all functional programming languages, but I am more interested from a Clojure standpoint (and Clojure examples)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

空宴 2024-09-13 02:55:20

现在有几个类似 ORM 的库正在开发中。

在邮件列表中,一些(聪明的)人最近描述了一些其他模型来说明如何实现这一点可能会起作用。这些库中的每一个都采用了相当不同的方法来解决问题,因此请务必查看所有库。

例如,这是一个使用 Oyako 的扩展示例。该库尚未准备好投入生产,并且仍在大力开发中,因此该示例可能在一周内无效,但它正在实现这一目标。无论如何,这都是一个概念验证。给它一些时间,有人会想出一个好的库。

请注意,clojure.contrib.sql 已经允许您从数据库中获取记录(通过 JDBC),并最终得到表示记录的不可变哈希映射。由于数据最终以法线贴图形式出现,因此所有适用于贴图的无数 Clojure 核心函数都已适用于该数据。

ActiveRecord 还给您什么?我可以想到几件事。

简洁的 SQL 查询 DSL

我在心里建模的方式是:首先定义表之间的关系。这不需要突变或对象。这是静态描述。 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 键来列出您的栏。

在Oyako,“数据地图”只是一张地图。查询本身是一个地图。返回的数据是地图向量(地图向量)。所以你最终会得到一个标准的、简单的方法来构建、操作和迭代所有这些东西,这很好。添加一些糖(宏和普通函数),让你更轻松地简洁地创建和操作这些地图,它最终变得非常强大。这只是一种方法,还有很多方法。

如果您查看像 Sequel 这样的库作为另一个示例,您会看到类似的内容:

Artist.order(:name).last

但是为什么这些函数必须是存在于对象内部的方法吗? Oyako 中的等效项可能是:

(last (-> (query :artist) 
          (order :name)))

保存/更新/删除记录

同样,为什么需要 OO 风格的对象或突变或实现继承?首先获取记录(作为不可变映射),然后通过一堆函数将其线程化,根据需要将新值关联到其上,然后将其放回数据库或通过调用函数将其删除在它上面。

聪明的库可以利用元数据来跟踪哪些字段已更改,以减少更新所需的查询量。或者标记记录,以便数据库函数知道将其放回到哪个表中。我认为,Carte 甚至可以进行级联更新(当父记录更改时更新子记录)。

验证、挂钩

我认为其中大部分属于数据库而不是 ORM 库。例如,级联删除(删除父记录时也删除子记录):AR 有办法做到这一点,但你可以只在 DB 中的表上扔一个子句,然后让你的 DB 处理它,再也不用担心了。与多种约束和验证相同。

但如果您想要钩子,可以使用普通的旧函数或多方法以非常轻量级的方式实现它们。在过去的某个时刻,我有一个数据库库,它在 CRUD 周期的不同时间调用钩子,例如 after-savebefore-delete。它们是根据表名进行调度的简单多方法。这使您可以根据需要将它们扩展到您自己的表。

(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 与非 OO 无关; AR 只是经过了很多打磨,因为我们投入了很多时间。也许 Clojure 还没有用于处理 DB 数据的 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 类方式编写代码的所有好处。

There are a couple ORM-like libraries in the works nowadays.

On the mailing list, some (smart) people recently described some other models for how this might work. Each of these libraries takes a fairly different approach to the problem, so be sure to take a look at them all.

Here's an extended example using Oyako, for example. This library isn't production ready and still under heavy development, so the example may be invalid in a week, but it's getting there. It's a proof-of-concept in any case. Give it some time and someone will come up with a good library.

Note that clojure.contrib.sql already lets you fetch records from a DB (via JDBC) and end up with immutable hash-maps representing records. Because the data ends up in normal maps, all of the myriad Clojure core functions that work on maps already work on this data.

What else does ActiveRecord give you? I can think of a couple of things.

A concise SQL-query DSL

The way I model this mentally: First you define the relationship between the tables. This doesn't require mutation or objects. It's a static description. AR spreads this information out in a bunch of classes, but I see it as a separate (static) entity.

Using the defined relationships, you can then write queries in a very concise manner. With Oyako for example:

(def my-data (make-datamap db [:foo [has-one :bar]]
                              [:bar [belongs-to :foo]]))

(with-datamap my-data (fetch-all :foo includes :bar))

Then you'll have some foo objects, each with a :bar key that lists your bars.

In Oyako, the "data map" is just a map. The query itself is a map. The returned data is a vector of maps (of vectors of maps). So you end up with a standard, easy way to construct and manipulate and iterate over all of these things, which is nice. Add some sugar (macros and normal functions), to let you concisely create and manipulate these maps more easily, and it ends up being quite powerful. This just just one way, there are a lot of approaches.

If you look at a library like Sequel for another example, you have things like:

Artist.order(:name).last

But why do these functions have to be methods that live inside of objects? An equivalent in Oyako might be:

(last (-> (query :artist) 
          (order :name)))

Save/update/delete records

Again, why do you need OO-style objects or mutation or implementation inheritance for this? First fetch the record (as an immutable map), then thread it through a bunch of functions, associng new values onto it as needed, then stuff it back into the database or delete it by calling a function on it.

A clever library could make use of metadata to keep track of which fields have been altered, to reduce the amount of querying needed to do updates. Or to flag the record so the DB functions know which table to stick it back into. Carte even does cascading updates (updating sub-records when a parent record is altered), I think.

Validations, hooks

Much of this I see as belonging in the database rather than in the ORM libary. For example, cascading deletes (deleting child records when parent records are deleted): AR has a way to do this, but you can just throw a clause onto the table in the DB and then let your DB handle it, and never worry again. Same with many kinds of constraints and validations.

But if you want hooks, they can be implemented in a very lightweight way using plain old functions or multimethods. At some point in the past I had a database library that called hooks at different times in the CRUD cycle, for example after-save or before-delete. They were simple multimethods dispatching on table names. This lets you extend them to your own tables as you like.

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

Then later as an end user I could write:

(defmethod before-delete ::my_table [x] 
  (if (= (:id x) 1)
    (throw (Exception. "OH NO! ABORT!"))
    x))

Easy and extensible, and took a couple seconds to write. No OO in sight. Not as sophisticated as AR maybe, but sometimes simple is good enough.

Look at this library for another example of defining hooks.

Migrations

Carte has these. I haven't thought much about them, but versioning a database and slurping data into it doesn't seem beyond the realm of possibility for Clojure.

Polish

A lot of the good of AR comes from all the conventions for naming tables and naming columns, and all the convenience functions for capitalizing words and formatting dates and such. This has nothing to do with OO vs. non-OO; AR just has a lot of polish because a lot of time has gone into it. Maybe Clojure doesn't have an AR-class library for working with DB data yet, but give it some time.

So...

Instead of having an object that knows how to destroy itself, mutate itself, save itself, relate itself to other data, fetch itself, etc., instead you have data that's just data, and then you define functions that work on that data: saves it, destroys it, updates it in the DB, fetches it, relates it to other data. This is how Clojure operates on data in general, and data from a database is no different.

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

Something like that. It's inside-out from the way objects work, but it provides the same functionality. But in Clojure you also get all the benefits of writing code in an FP kind of way.

绅刃 2024-09-13 02:55:20

这个问题已经有一段时间没有得到解答了,但是 Korma http://sqlkorma.com 是另一个项目,也有助于减少SQL 和 Clojure 之间的不一致。它是最近才开始开发的,应该可以与更新的 Clojure 版本一起使用。

This question hasn't been answered in a while, but Korma http://sqlkorma.com is another project that also helps reduce the dissonance between SQL and Clojure. It's been worked on more recently and should work with more recent Clojure versions.

妥活 2024-09-13 02:55:20

基于 clojure.contrib.sql 构建的新的 micro SQL DSL 已经启动。它位于 Github 上,重量轻,大小为 76 loc。

作者的博客上有很多 DSL 背后的示例和理论,链接在他的 Github 个人资料上(新用户 SO 超链接限制啊)。我觉得它的设计允许将 SQL 抽象为更惯用的 Clojure。

A new micro SQL DSL built upon clojure.contrib.sql has been started. It's on Github weighing in at a lightweight 76 loc.

There's a lot of examples and theory behind the DSL on the author's blog, linked on his Github profile (new user SO hyperlink limit bah). Its design allows SQL to be abstracted into much more idiomatic Clojure, I feel.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文