我有一个问题,关于什么是Datomic的更惯用的架构。
假设我们有实体User
,Post
和Topic
。
Post
可以属于Topic
,User
和其他Post
(回复)。现在,我应该,
a)创建一个:posts
属性,这只是Post
的列表,并将其注入需要引用多个Post
s的每个实体中?
或
b)建立更明确的关系,以使Post
具有:post/author
属性作为对用户的引用,并且可能是:post/belongs-to
属性,可以引用任一{ {1}}或其他Topic
?
观察:
如果我 b ,我似乎会获得更多的语义关系。我可以举例Post
,它更能描述其关系网的性质,而不是(:post/_author user-entity)
(因为,(:posts user-entity)
有User
是什么意思?那些被:posts
收藏的User
,作者Post
,或者是什么?)
b 的另一个副作用是我可以在不改变任何其他实体的情况下创建新的Post
。如果我执行 a ,我需要创建Post
并将其插入Post
的{{1}}属性中,需要两次操作而不是一次。
但是,我觉得 a 可能是更惯用的做法。例如,如果:posts
引用User
而不是:posts
,那么我是否应该更容易看到属性列表User
如何随时间发生变化?通过:posts
属性Post
引用User
。
什么是更好的,为什么?
答案 0 :(得分:6)
您的选项( b )本质上是在datomic中使用的惯用语和唯一方式。
所有数据库模式仅被编码为属性可以在实体 - 属性 - 值数据(EAV)的结构中采用的值。
请参阅http://docs.datomic.com/schema.html - 从文档中获取的主要建议是:
每个Datomic数据库都有一个模式,用于描述可与实体关联的属性集。 架构仅定义属性本身的特征。它没有定义哪些属性可以与哪些实体相关联。
实体本身是高度抽象的(内部只是数字),实体的所有有趣属性都被编码为属性值断言。实体未输入!您可以通过为其声明的属性创建实体的语义,例如:user / firstname,:post / title,:post / content,:topic / description等。 这就是为什么你真的想要命名属性的原因。
这种情况的一个特例是属性类型:db.type/ref
,其中EAV中的“V”值本身就是另一个实体。这就是在实体之间创建语义关联的原因。您为每个属性指定一个“名称”(作为:db/ident
)以捕获E< - > E连接实际意味着什么。因此,您可以将:db.type/ref
与:db/ident
“:post / author”的属性设为{。
请注意,所有:db.type/ref
属性本质上都是双向的,因此如果Eu
是表示用户的实体而Ep
是表示帖子的实体,那么以下内容在数据创建和查询:
[Ep :post/author Eu]
[Eu :post/_author Ep]
所有实体关系只是更多属性断言,非常灵活。如果您以后想要添加收藏帖子的概念,那么它只是:db.type/ref
的另一个属性。使用:db/ident
创建它,例如“:user / favorites”,并在先前存在的用户和帖子之间建立连接(将不同的用户实体作为作者)。
[aUser :user/favorites somePost]
没有集合值属性的概念,因此您在( a )中建议的内容在datomic中无法正确表达。您将使用查询来聚合帖子。删除后将通过撤回实体本身来建模。这样一个收回的帖子将在数据库历史记录中保持可见。
这确实对如何指定实体列表的订单提出了挑战。您需要使用“自然”排序,例如帖子的日期(在数据组事务中捕获或作为帖子的显式属性)或使用基于显式属性值的排序,例如通过:post / up-投票数字属性。
如果您需要实体的语义分组,其中“子实体”仅有意义并且仅作为更大的事物的一部分存在 - 例如订单中的订单项实体 - 请参阅datomic components。
答案 1 :(得分:0)
我认为这主要取决于您的访问模式。如果您每次访问可以嵌入相关帖子的实体时做都需要这些帖子,那么嵌入它们是有意义的( a )。如果您大多数时间单独访问它们,那么将它们分开可能会更好( b )。
或者您可以同时执行这两项操作( c ),将单独的Post实体视为规范实体,将各种实体中的嵌入实体视为缓存版本。这样,您需要一个脚本/批处理,每次更新规范版本时都会更新嵌入的帖子。这使得所有读取更容易,因为信息始终存在,但写入更复杂,因为您需要自己保持同步。此模式只有在您可以接受规范版本和嵌入版本之间的某些不一致时才可用,并且重新同步的延迟对您来说并不重要。
注意:这个建议与Datomic没有任何关系,这些是从NoSQL世界借来的技术,并不代表我是专家。