Datomic 简易入门

注: 本文翻译自 Datomic for Five Year Olds. 翻译的风格比较凌乱,
请各位看官见谅 :P

如果你对 Datomic 感兴趣想一探究竟, 然后发现,
为了弄明白这货到底是干啥的你需要通读足足3个演讲, 8个访谈和无数篇的文章,
那你很可能当场就给跪了...

别怕! 我已经帮你全部搞定! (看你那一脸迷人的微笑, 我就知道我抵挡不了 -.-)
读过本文之后你将对 Datomic 独特的 三大核心 和几个关键术语了然于心.
当你日后深入研究 Datomic 时, 你会发现这些知识对于上手 Datomic 也是极好的~

Datomic 的三大核心 --- 惊鸿一瞥

Datomic 跟其他数据库的不同之处体现在它的 信息模型, 体系结构可编程性 上.
下面是快速一览, 之后一一详细介绍.

关系型数据库(Relational DB) 无模式数据库(Schemaless DB) Datomic
信息模型 信息的单位是 实体(entity), 对它的修改会覆盖原来的值 信息的单位是 无模式的文档(schemaless document), 也是就地修改, 覆盖原来的值 信息的单位是由 实体,属性,值和时间构成的 事实(fact). 相对于就地修改, 你通过声明或撤销事实来扩充数据库
体系结构 数据库是个庞大的系统, 整体负责 查询,ACID保证存储 与关系型数据库相同 查询,事务处理存储相互独立, 从而提供 ACID保证, 读操作扩展性以及更强大的能力
可编程性 通过字符串操作生成 SQL 操作特定格式的数据结构, 在编程上比使用 SQL 更友好, 但没有 SQL 强大 通过 Datalog 完全使用数据结构进行操作, 而且和 SQL 一样强大

核心一: 信息模型

一个数据库的 信息模型 取决于 实体属性 的相互关系 ---
我们姑且称之为 模式系统 (schema system).

没错, 在数据库的选型上, 最主要的考虑因素就是选择关系型数据库还是无模式数据库,
因为这将对之后的软件设计和架构产生很大的影响. 马上你就会看到, Datomic 的
模式系统 不像关系型数据库那样呆板, 但提供的能力比无模式数据库要强大的多.

此外, 数据库的 信息模型 还体现在它如何对待 时间. Datomic 对待 时间
的方式不同于绝大多数其他数据库.

下面我们就看看关系型数据库,无模式数据库和 Datomic 各自的 信息模型,
并比较一下它们对 时间 的处理方式.

关系模式 (Relational Schemas)

相信大家对 关系模型 已经驾轻就熟了. 其要点是:

实体 (Entity) 实体即一个 关系(relation) (即数据库表格) 内的 元组(tuple), 它包含一套固定的属性(值). 说人话! -- 实体就是数据库表格(table)中的某一行(row).
属性 (Attribute) 属性在定义上等于 名称(name) + 数据类型(data type), 对应到数据库表格(table)中的列(column). 属性无法脱离数据库表格而独立存在. 不同表的属性之间是在逻辑上毫不相干, 即使它们的名称和类型相同.

关系模式

关系模型最大的特点也许就是严格而呆板. 每个实体(即表中的某一行)必须属于某个严格定义的关系(即表格).
如果想给实体添加或删除属性, 必须得修改表格的结构, 而这样会影响到表中的所有实体.

无模式 (Schemaless)

为了解决关系型数据库过于严格呆板的问题, 无模式(NoSQL)数据库应运而生.
这类数据库提供了一些数据组织的新概念和工具(例如 MongoDB 的 collections).
与关系型数据库不同的是, 这些概念不限制实体的具体结构.
然而缺乏结构性也是有代价的: 查询能力不够强大, 而且得由你的应用程序来保持数据和结构的完整一致.

实体 (Entity) 在此我们将实体看作文档(document), 它可以包含任何属性
属性 (Attribute) 属性只需定义一个名称(name), 它可以存放任意类型的值. 在逻辑上, 一个实体中的某个属性与其他任何属性都是完全不同的, 包括位于其他实体中同属一个 collection 的那些属性. 属性间的关联由应用程序来定义和保证.

无模式

Datomic 中的模式 (Datomic Schemas)

在 Datomic 中, 模式(schema)是一套核心属性, 实际起到定义数据类型的作用.
实体可以包含任何已定义的属性. 这样 Datomic 中的实体比无模式数据库中的结构性更强,
但是比关系型数据库更灵活. 此外, 你在拥有匹敌于关系模型的强大查询能力的同时,
也不需要在应用程序里纠结如何保持数据和结构的完整一致.

实体 (Entity) 实体即一组 属性/值 的映射(map). 实体不需要有固定的结构, 可以包含任何在模式(schema)中定义的属性.
属性 (Attribute) 属性 = 名称(name) + 类型(data type) + 基数(cardinality). 属性本身可以作为数据类型来看待, 因为相对于关系模型中从属于某个表结构的属性, Datomic 中的属性是独立存在的.

Datomic 中的模式

时间 (Time)

如果你不了解 Rich Hickey 对时间(time),身份(identity),值(value)和状态(state)相关的思考,
那么 Datomic 对待时间的方式对你来说可能会有点陌生. 你可以通过我的这篇快速了解一下:
"The Unofficial Guide to Rich Hickey's Brain"

在关系型和无模式数据库的世界里是没有 时间 这个概念的, 它总是处于 "现在" 这个状态,
不保存历史状态. 当你更新或删除数据库中的某一行之后, 你就再也没法得到那一行原来的状态了.

这种方式会导致很多头疼的问题. 若你也经历过为了完成一个单元的任务却要来回访问数据库好几次,
你就会懂我的意思. 对数据库的每一次访问, 你都得纠结于数据库是不是又被修改了.
你下定决心使用各种奇技淫巧来防止竞态条件(race conditions), 最终还是找不到完美的解决方案.

除此之外, 查询历史状态几乎是不可能完成的任务. 你可能觉得这没什么大不了的,
因为你已经习惯了, 但是可以轻松的查询历史状态会是一项非常强大的能力.

在 Datomic 中 时间 是 "一级公民" (first-class concept).
对数据库的所有更改都通过一条条 事务(transaction) 进行, 每一条事务都包含一个时间戳(timestamp).
当更新一个实体时, Datomic 并不覆盖原来的状态, 而是增加一个新的状态并保留原来的状态.
这样你可以获取一个实体在之前任一时刻的状态.

时间 (Time)

我们也可以把一个 Datomic 数据库理解成按时间先后排列的 值(values) 的集合(collection).
这里的"值"指的是整个数据库在某一刻的状态 --- 包含实体和属性的一个整体. 当你通过
事务(transaction) 创建,更新或删除实体时, 相当于把一个新的数据库 值(value)
加到集合里. 任何时候你都可以对 Datomic 说: "把此刻的数据库值给我看看".

Datomic 的信息模型 --- 总结

在 Datomic 中信息的单位是 事实(fact), 由实体,属性,值和时间构成.
比方说, "公主" 不是 事实, "公主最爱的佐料" 也不是, "公主最爱的佐料是芥末"
就很接近了, "公主在2014年2月10号晚上10点时最爱的佐料是芥末" 才是纯正的 事实.

事实不会消失. 就算以后公主的最喜欢的佐料变了, 也无法改变公主在之前那个时刻最喜欢芥末的事实,
而且知道这个旧事实可能会很有用. 在 Datomic 中新的事实不会抹掉旧的事实, 所有的历史事实都会保留下来.
实际上在 Datomic 中有一个术语专门用于表示事实: "datom" (英语复数: datoms).

更详细的内容可以参见 Datomic 的作者(Rich Hickey)的文章: The Datomic Information Model

核心二: 体系结构

市面上大多数的数据库都是把下列功能耦合在一起的庞然大物:

  • 存储

Datomic 解藕了这些功能. 一个单独的 事务处理器(transactor) 负责所有的写操作和 ACID 保证.
这是和其他数据库唯一的一点相似的地方.

注: Datomic Architectural Overview 这个页面上的示意图糟透了, 又难看又难懂.
那个箭头是神马意思? 虚线边框又是何解? "Comm" 是特么怎么回事? 求给改好点吧,
我给跪下了成不? 嗯, 吐槽完毕.

你的应用程序将包含一套工具库组件用来与 Datomic 通信, 即 小伙伴专用连接器(Peer Library).
使用 Peer Library 访问 Datomic 数据库的 应用程序 就成了数据库的 小伙伴(Peer)
之一. 我猜 Datomic 那群人是故意不想用 "客户端(client)" 这个词, 不过 Peer
本质上就相当于客户端. 跟一般的数据库客户端不同的是, 数据库的查询操作是在 Peer
里完成的.

一般的数据库是由客户端发送查询请求到数据库服务器, 数据库执行查询之后把结果发送给客户端.
在 Datomic 中则是由 Peer Library 负责将 数据库值(value of the database) 取到 Peer
并执行查询, 也就是说查询操作真真的就发生在你的应用程序里. 若是用甄嬛来演绎一下, 就好比:

Peer: Peer Library 啊! 若是能找到传说中的那群会弹古筝的猫咪给本宫观赏一下, 也是极好不过的~

Peer Library: 遵命! 属下去去就来! 数据库, 快把数据给我!

(Peer Library 获取数据库后执行查询)

Peer Library: 参见娘娘! 古筝猫咪带到!

Peer: 真真是极好的!

其中最神奇的地方就是那个 "获取数据库". 在 Datomic 中整个数据库的数据被看作是一个值.
Peer Library 会负责从数据库中取得刚好足够的数据来满足你的查询操作.

从而, 大部分的数据库操作可以直接在你的应用程序内运行, 而不是运行于一个中央服务器,
这使得对读操作的扩展变得易如反掌. 它把更强大的数据库操作能力给了你的应用程序,
而不是把所有功能都局限在一个中央服务器上(以至于需要考虑瓶颈问题).

最后, Datomic 没有发明自己的存储系统, 而是把存储功能当作一个服务"外包"出去,
允许你选择你想要的存储后端(storage backend). 目前 Datomic 支持这些作为存储后端:

  • 文件系统
  • DynamoDB
  • Riak
  • Couchbase
  • Infinispan
  • 关系型数据库 (SQL database)

核心三: 可编程性

在 Datomic 的世界里一切皆数据! 模式定义(schema)是数据! 查询语句是数据!
事务也是数据! 这太棒了, 因为操作有结构的数据要容易的多, 与那些疯狂的字符串操作相比的话.
看看这些查询语句的示例.

参考资料

结束

好了, 目前就这么多. 有任何建议或改进的话都请告诉我.

注: 对于原文的建议或改进可以到原文发表评论

2014-02-11 00:00732datomicdb