我有一个用户实体类型在我的Datomic数据库可以遵循其他用户类型。当一个用户关注另一个已经关注他们的用户时,我的问题就出现了:
User A follows user B and also User B follows user A
当我尝试序列化(使用Cheshire)时,我得到一个StackOverflowError,因为(我猜):user/follows-users
属性的无限递归。
我该如何序列化(API的json)两个以这种方式相互引用的数据实体?
这是一个基本的模式:
; 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)
链接数据结构的动态扩展只有在没有循环的情况下才是安全的。一个承诺"只在找到周期之前热切地扩展数据,然后切换到链接(根据用户id)"的api可能比一个从不扩展且总是返回足够的用户来关注响应中的所有链接的api更难可靠地使用。例如,上面的请求可以返回JSON:
[{"id": -100000,
"username": "Cheech",
"follows-users": [-200000]}
{"id": -200000,
"username": "Chong",
"follows-users": [-100000]}]
其中所选用户的列表是通过将用户图的遍历简化成一个集合来找到的。
我对Datomic有点不熟悉,我确信一定有一种更习惯的方式来做@arthur-ulfeldt上面建议的事情,但是如果其他人正在寻找一个关于如何将Datomic EntityMaps序列化为json的快速指针,其中存在一个自引用ref,这里的代码解决了我的问题:
(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)