首页 文章

Datomic中的数据建模

提问于
浏览
57

我一直在调查Datomic,它看起来很有趣 . 但是虽然似乎有very good information on how Datomic works technically,但我还没有看到应该如何考虑数据建模 .

Datomic中的数据建模有哪些最佳实践?这个问题有什么好的资源吗?

2 回答

  • 125

    警告Lector

    由于Datomic是新的,我对它的体验有限,因此不应将此答案视为最佳实践 . 将此作为Datomic的介绍,为那些具有关系背景和渴望更高效数据存储的人提供 .

    入门

    在Datomic中,您将域数据建模为具有属性值的实体 . 因为对另一个实体的引用可以是属性的值,所以您可以简单地对实体之间的关系进行建模 .

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

    这种相似性很好,因为您可以在建模域时勾画出传统的ER图 . 您可以像在SQL数据库中一样依赖关系,但不需要乱用外键,因为这是为您处理的 . 在Datomic中写入是事务性的,并且您的读取是一致的 . 因此,无论粒度感觉如何,您都可以将数据分成实体,依靠连接来提供更大的图像 . 对于许多NoSQL存储而言,这是一种便利,在更新过程中,通常会有大的非规范化实体来实现某些有用的原子级 .

    在这一点上,你有一个良好的开端 . 但Datomic比SQL数据库灵活得多 .

    优势

    时间本质上是所有Datomic数据的一部分,因此无需在数据模型中专门包含数据历史记录 . 这可能是Datomic最受关注的方面 .

    在Datomic中,您的架构未在SQL所需的"rectangular shape"中严格定义 . 也就是说,实体1可以具有满足模型所需的任何属性 . 对于不适用于它的属性,实体不需要具有 NULL 或默认值 . 您可以根据需要向特定的单个实体添加属性 .

    因此,您可以随着时间的推移更改单个实体的形状,以响应您域中的更改(或更改您对域的理解) . 所以呢?这与MongoDB和CouchDB等文档存储区不同 .

    不同之处在于,使用Datomic,您可以在所有受影响的实体上以原子方式实现架构更改 . 这意味着您可以根据任意域逻辑发布事务以更新所有实体的形状,这将在不提交读取器的情况下执行,直到提交为止 . 我不知道在关系文档存储空间或文档存储空间中有任何接近这种能力的东西 .

    您的实体也没有严格定义为"living in a single table" . 您决定在Datomic中定义实体的"type"的内容 . 您可以选择明确并强制模型中的每个实体都具有 :table 属性,该属性意味着它是"type" . 或者,您的实体只需满足每种类型的属性要求即可符合任意数量的"types" .

    例如,您的模型可以强制要求:

    • 一个人需要属性 :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}
    

    可以推断为 PersonResident but NOT _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

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

    最后,OO和关系世界之间的关系一直让我感到沮丧 . 使用功能性的,以数据为中心的语言(Clojure)可以帮助实现这一目标,但Datomic希望提供一种耐用的数据存储,不需要心理体操从代码桥接到存储 .

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

    这使得数据建模更加自然,而且在我看来更不用说了 .

    潜在的陷阱

    • 冲突的属性

    上面的例子说明了模型中的潜在缺陷 . 如果您以后决定 :id 也是 Employee 的属性怎么办?解决方案是将属性组织到命名空间中 . 所以你会得到 :member/id:employee/id . 提前做这件事有助于避免以后发生冲突 .

    • 无法更改属性的定义(尚未)

    一旦你以后再改变它 . 我们在这里用SQL术语谈论 ALTER TABLE ALTER COLUMN . 现在,您可以使用正确的定义创建替换属性并移动现有数据 .

    这可能听起来很可怕,但它确实很糟糕 . 它实质上是RDBMS在发布 ALTER TABLE 时在幕后所做的事情,但是您可以命名规则 .

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

    灵活的架构并没有建议一些前期规划,以一种理智的方式对事物进行建模,就像对待任何其他数据存储一样 . 在必要时,利用Datomic的灵活性,而不仅仅是因为你可以 .

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

    Datomic isn 't a good data store for BLOBs or very large data that'不断变化 . 因为它保留了以前值的历史记录,并且还没有一种方法可以清除旧版本(尚未) . 这种东西几乎总是更适合像S3这样的对象存储 . 更新:有一种方法disable history on a per-attribute basis .

    资源

    注意事项

    • 我的意思是行意义上的实体,而不是更恰当地描述为实体类型的表意义 .

    • 我的理解是目前支持Java和Clojure,但未来可能会支持其他JVM语言 .

  • 3

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

    • 如果存储许多类似但不相等的实体"types"或模式,请在模式中使用type关键字,如 ``
    [: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}
    

    当你阅读它们时,从查询中获取entity-id并使用 datomic.api/entityeid 并通过在类型上调度多方法来解析它们,因为在一些更复杂的模式中很难对所有属性进行良好的查询 .

相关问题