64

我一直在研究Datomic,它看起来非常有趣。但是,虽然似乎有关于 Datomic 在技术上如何工作的非常好的信息,但我还没有看到太多关于应该如何考虑数据建模的信息。

Datomic 中的数据建模有哪些最佳实践?有没有关于这个主题的好资源?

4

2 回答 2

130

警告讲师

由于 Datomic 是新的并且我对它的经验有限,因此不应以任何方式将此答案视为最佳实践。将此作为 Datomic 的介绍,适用于那些具有关系背景并渴望更高效的数据存储的人。

入门

在 Datomic 中,您将域数据建模为拥有Values for Attributes的实体。因为对另一个实体的引用可以是 Attribute 的值,所以可以简单地对实体之间的关系进行建模。

乍一看,这与在传统关系数据库中建模数据的方式并没有什么不同。在 SQL 中,表行是实体,表的列名称是具有Values的属性关系由一个表行中的外键值表示,该外键值引用另一表行的主键值

这种相似性很好,因为您可以在对域进行建模时勾勒出传统的 ER 图。您可以像在 SQL 数据库中一样依赖关系,但不需要弄乱外键,因为这是为您处理的。Datomic 中的写入是事务性的,您的读取是一致的。因此,您可以以任何合适的粒度将数据分成实体,依靠连接来提供更大的图景。这是您在许多 NoSQL 存储中失去的便利,在这些存储中,通常拥有大型非规范化实体以在更新期间实现某种有用的原子性水平。

在这一点上,你有了一个好的开始。但 Datomic 比 SQL 数据库灵活得多。

利用

时间本质上是所有 Datomic 数据的一部分,因此无需将数据历史记录作为数据模型的一部分。这可能是 Datomic 最受关注的方面。

在 Datomic 中,您的模式没有严格定义为 SQL 所需的“矩形”。也就是说,实体1可以具有满足您的模型所需的任何属性。实体不需要具有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}

可以推断为 a Person、 aResident aMember 但不是a Employee

Datomic 查询在Datalog中表达,并且可以合并以您自己的语言表达的规则,引用未存储在 Datomic 中的数据和资源。您可以将数据库函数存储为 Datomic 内部的一等值。这些类似于 SQL 中的存储过程,但可以作为事务内部的值进行操作,并且也可以用您的语言编写。这两个功能都可以让您以更以域为中心的方式表达查询和更新。

最后,OO 和关系世界之间的阻抗不匹配一直让我感到沮丧。使用功能性的、以数据为中心的语言 (Clojure) 有助于实现这一点,但 Datomic 看起来提供了一种持久的数据存储,它不需要通过脑力体操从代码到存储进行桥接。

例如,从 Datomic 获取的实体看起来和行为类似于 Clojure(或 Java)映射。它可以传递到更高级别的应用程序,而无需转换为对象实例或通用数据结构。遍历该实体的关系将从 Datomic 懒惰地获取相关实体。但是保证它们与原始查询一致,即使面对并发更新。这些实体看起来就像是嵌套在第一个实体中的普通旧地图。

在我看来,这使得数据建模更自然,更不用说一场战斗了。

潜在的陷阱

  • 冲突的属性

    上面的示例说明了模型中的潜在缺陷。如果您后来决定这:id也是 的一个属性Employee怎么办?解决方案是将您的属性组织到名称空间中。所以你会同时拥有:member/id:employee/id。提前这样做有助于避免以后发生冲突。

  • 一个属性的定义不能改变(还)

    一旦您将 Datomic 中的属性定义为特定类型、索引与否、唯一等,您以后就无法更改它。我们在ALTER TABLE ALTER COLUMN这里用 SQL 用语说话。现在,您可以创建具有正确定义的替换属性并移动现有数据。

    这听起来可能很糟糕,但事实并非如此。由于事务是序列化的,您可以提交一个创建新属性、将数据复制到其中、解决冲突并删除旧属性的事务。它将在不受其他事务干扰的情况下运行,并且可以利用您的母语中的特定于域的逻辑来完成它。这本质上是 RDBMS 在您发出 时在幕后所做的事情ALTER TABLE,但您为规则命名。

  • 不要做“糖果店里的孩子”

    灵活的模式并不意味着没有数据模型。我建议一些前期计划以理智的方式对事物进行建模,就像对任何其他数据存储一样。在必要时利用 Datomic 的灵活性,而不仅仅是因为可以。

  • 避免存储大量、不断变化的数据

    对于 BLOB 或不断变化的非常大的数据,Datomic 不是一个好的数据存储。因为它保留了以前值的历史记录,并且没有清除旧版本的方法(还)。这种东西几乎总是更适合像 S3 这样的对象存储。更新:有一种方法可以在每个属性的基础上禁用历史记录。更新:现在还有一种方法可以删除数据;然而,存储对外部对象的引用而不是对象本身可能仍然是处理 BLOB 的最佳方法。将此策略与使用字节数组进行比较。

资源

笔记

  1. 我的意思是行意义上的实体,而不是更恰当地描述为实体类型的表格意义上的实体。
  2. 我的理解是目前支持 Java 和 Clojure,但未来可能支持其他 JVM 语言。
于 2012-05-13T03:14:28.433 回答
3

来自 bkirkbri 的一个非常好的答案。我想做一些补充:

  1. 如果您存储许多相似但不等于“类型”或模式的实体,请在模式中使用类型关键字,例如

    [: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}

当您阅读它们时,从查询中获取实体 ID 并使用datomic.api/entity,并eid在必要时通过按类型分派的多方法解析它们,因为在某些更复杂的模式中很难对所有属性进行良好的查询。

于 2013-08-26T18:16:30.507 回答