在Datomic中序列化递归引用

时间:2014-05-12 22:05:11

标签: json recursion clojure datomic

我的Datomic数据库中有一个用户实体类型,可以跟随其他用户类型。当一个用户跟随另一个已经关注他们的用户时,我的问题出现了:

User A follows user B and also User B follows user A

当我尝试序列化(使用Cheshire)时,由于(我猜测):user/follows-users属性的无限递归,我得到了StackOverflowError。

我将如何序列化(以json为API)两个以这种方式相互引用的Datomic实体?

这是一个基本架构:

; schema
[{:db/id #db/id[:db.part/db]
:db/ident :user/username
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db/unique :db.unique/identity
:db.install/_attribute :db.part/db}

{:db/id #db/id[:db.part/db]
:db/ident :user/follows-users
:db/valueType :db.type/ref
:db/cardinality :db.cardinality/many
:db.install/_attribute :db.part/db}

; create users  
{:db/id #db/id[:db.part/user -100000]
 :user/username "Cheech"} 
{:db/id #db/id[:db.part/user -200000]
 :user/username "Chong"}

; create follow relationships
{:db/id #db/id[:db.part/user -100000]
 :user/follows-users  #db/id[:db.part/user -200000]}
{:db/id #db/id[:db.part/user -200000]
 :user/follows-users  #db/id[:db.part/user -100000]}]

一旦数据库在repl上设置等等:

user=> (use '[cheshire.core :refer :all])
nil

user=> (generate-string (d/touch (d/entity (d/db conn) [:user/username "Cheech"]))) 
StackOverflowError   clojure.lang.RestFn.invoke (RestFn.java:433)

2 个答案:

答案 0 :(得分:1)

链接数据结构的急切扩展只有在没有周期的情况下才能安全地使用任何语言。一个api,承诺"急切扩展数据,直到找到一个循环,然后切换到链接(通过用户ID)"可能比从未扩展的用户更难以可靠地消费,并且总是返回足够的用户来跟踪响应中的所有链接。例如,上面的请求可以返回JSON:

[{"id": -100000,
  "username": "Cheech",
  "follows-users": [-200000]}
 {"id": -200000,
  "username": "Chong",
  "follows-users": [-100000]}] 

通过将用户图表的步行减少到集合中来找到所选用户的列表。

答案 1 :(得分:0)

我对Datomic有点n00b并且肯定必须有一种更惯用的方法来做@ arthur-ulfeldt上面提出的建议,但是如果有其他人正在寻找关于如何序列化Datomic的快速指针EntityMaps进入json,其中存在自引用引用,这是解决我的问题的代码:

(defn should-pack?
  "Returns true if the attribute is type 
  ref with a cardinality of many"
  [attr]
  (->>
    (d/q '[:find ?attr
           :in $ ?attr
           :where
           [?attr :db/valueType ?type]
           [?type :db/ident :db.type/ref]
           [?attr :db/cardinality ?card]
           [?card :db/ident :db.cardinality/many]]
         (d/db CONN) attr)
    first
    empty?
    not))

(defn make-serializable 
  "Stop infinite loops on recursive refs" 
  [entity]
  (def ent (into {} entity))
  (doseq [attr ent]
    (if (should-pack? (first attr))
      (def ent (assoc ent 
                      (first attr) 
                      (map #(get-entity-id %) (first (rest attr)))))))
  ent)