Datomic中的数据建模
我一直在研究Datomic,它看起来非常有趣。 尽管似乎有关于Datomic如何在技术上工作的非常好的信息,但我还没有看到如何考虑数据建模。
Datomic中的数据建模有哪些最佳实践? 这方面有没有很好的资源?
注意Lector
由于Datomic是新的,我的经验很有限,因此不应以任何方式将此答案视为最佳实践。 把它作为Datomic的介绍给那些具有关系背景和渴望更高效的数据存储的人。
入门
在Datomic中,您将域数据建模为拥有属性值的实体。 由于对另一个实体的引用可以是属性的值,因此您可以简单地为实体之间的关系建模。
首先看,这与数据在传统关系数据库中建模的方式并不完全相同。 在SQL中,表行是实体和具有值的表的列名称属性。 关系由引用另一个表行的主键值的一个表行中的外键值表示。
这种相似性很好,因为您可以在建模域时勾画出传统的ER图。 您可以像在SQL数据库中那样依赖关系,但不需要混淆外键,因为这是为您处理的。 在Datomic中写入是事务性的,您的读取是一致的。 因此,您可以将数据分隔为任何粒度感觉良好的实体,依靠联接来提供更大的图像。 对于许多NoSQL商店来说,这是一个很方便的地方,在这些商店中,通常会有大量非规范化实体在更新期间实现一些有用的原子级别。
在这一点上,你是一个好开始。 但是Datomic比SQL数据库更灵活。
利用
时间本质上是所有Datomic数据的一部分,所以不需要将数据的历史记录作为数据模型的一部分。 这可能是Datomic最受关注的一个方面。
在Datomic中,您的模式不是在SQL所需的“矩形”中严格定义的。 也就是说,entity1可以拥有任何它需要的属性来满足你的模型。 对于不适用于其的属性,实体不需要具有NULL
或默认值。 如果您认为合适,您可以将属性添加到特定的单个实体。
因此,您可以随时改变单个实体的形状,以响应域中的更改(或更改您对域的理解)。 所以呢? 这与文档存储(如MongoDB和CouchDB)不同。
与Datomic不同的是,您可以在所有受影响的实体上自动执行模式更改。 这意味着你可以发出一个事务来更新所有实体的形状,根据任意域逻辑, 用你的语言编写 [2],它将在不影响读者的情况下执行,直到提交。 在关系或文档存储空间中,我没有意识到任何接近这种功能的东西。
您的实体也并非严格定义为“生活在一张桌子上”。 您决定什么定义了Datomic中实体的“类型”。 你可以选择明确,并要求模型中的每个实体都有一个:table
属性,它暗示了它的“类型”。 或者您的实体只需满足每种类型的属性要求即可符合任意数量的“类型”。
例如,您的模型可能要求:
:name
, :ssn
, :dob
:name
, :title
, :salary
:name
, :address
:id
, :plan
:expiration
这意味着像我这样的实体:
{:name "Brian" :ssn 123-45-6789 :dob 1976-09-15
:address "400 South State St, Chicago, IL 60605"
:id 42 :plan "Basic" :expiration 2012-05-01}
可以推断为一个Person
,一个Resident
和一个Member
但不是一个Employee
。
Datomic查询在Datalog中表达,并且可以包含用您自己的语言表达的规则,引用未存储在Datomic中的数据和资源。 您可以将数据库函数作为Datomic中的第一类值存储。 这些类似于SQL中的存储过程,但可以作为事务内部的值进行操作,也可以用您的语言编写。 这两个功能都可让您以更加以领域为中心的方式表达查询和更新。
最后,面向对象和关系世界之间的阻抗不匹配一直让我感到沮丧。 使用功能性,以数据为中心的语言(Clojure)可以帮助解决这个问题,但Datomic希望提供一种持久的数据存储,不需要心理体操从代码到存储。
作为一个例子,从Datomic获取的实体看起来像Clojure(或Java)映射。 它可以传递到更高级别的应用程序,而不需要翻译成对象实例或通用数据结构。 遍历该实体的关系将会懒惰地从Datomic获取相关实体。 但保证他们将与原始查询保持一致,即使面对并发更新。 这些实体看起来是嵌套在第一个实体内的普通旧地图。
这使得数据建模更加自然和多,更不用说在我看来是一场斗争。
潜在的缺陷
相冲突的属性
上面的例子说明了你的模型中潜在的缺陷。 如果您稍后决定:id
也是Employee
的属性? 解决方案是将您的属性组织到名称空间中。 所以你可以同时拥有:member/id
和:employee/id
。 提前做此事有助于避免以后发生冲突。
属性的定义不能改变(还)
一旦您将Datomic中的属性定义为特定类型,索引或不索引,唯一等,您将无法在以后更改该属性。 我们在这里用SQL语句说ALTER TABLE ALTER COLUMN
。 现在,您可以使用正确的定义创建替换属性并移动现有数据。
这听起来可怕,但事实并非如此。 因为事务是序列化的,所以您可以提交一个创建新属性,将数据复制到它,解决冲突并删除旧属性。 它将在没有其他事务干扰的情况下运行,并且可以利用本地语言中特定于领域的逻辑来完成这件事。 这基本上就是RDBMS在发布ALTER TABLE
时在后台执行的操作,但是您将规则命名。
不要成为“糖果店里的小孩”
灵活的模式并不意味着没有数据模型。 我建议一些前期规划,以与其他数据存储相同的方式对事物建模。 当你需要时,利用Datomic的灵活性,不仅仅是因为你可以。
避免存储大量不断变化的数据
对于BLOB或不断变化的非常大的数据,Datomic不是一个好的数据存储。 因为它保留了之前值的历史记录,并且没有清除旧版本的方法(尚未)。 这种东西几乎总是适合像S3这样的对象存储。 更新:有一种方法可以根据每个属性禁用历史记录。
资源
笔记
来自bkirkbri的一个非常好的答案。 我想做一些补充:
如果存储了许多类似但不相同的“类型”或模式的实体,请在模式中使用类型关键字,如
[:db/add #db/id[:db.part/user] :db/ident :article.type/animal] [:db/add #db/id[:db.part/user] :db/ident :article.type/weapon] [:db/add #db/id[:db.part/user] :db/ident :article.type/candy]{:db/id #db/id[:db.part/db] :db/ident :article/type :db/valueType :db.type/ref :db/cardinality :db.cardinality/one :db/doc "The type of article" :db.install/_attribute :db.part/db}
当你阅读它们时,从查询中获得实体datomic.api/entity
并使用datomic.api/entity
和eid
,然后通过multimethods根据类型进行分析(如果需要的话),因为很难对一些更复杂的模式中的所有属性进行很好的查询。