如何防止事务违反 Datomic 中的应用程序不变量
How to prevent transactions from violating application invariants in Datomic
详细来说,大多数关系数据库都有数据库约束的概念。这是 Postgres documentation on constraints。 Datomic 提供哪些工具来约束数据或维护存储数据的一些不变量集?
一种方法是使用修改数据的事务函数,并在修改期间进行约束验证:http://docs.datomic.com/database-functions.html#uses-for-transaction-functions
编辑 2019-06-28: 自 0.9.5927
(Datomic On-Prem)/480-8770
(Datomic Cloud)以来,Datomic 支持更精细的写入-通过 Attribute Predicates, Entity Specs and Entity Predicates 进行时间验证。这使得大部分初始答案无效或无关紧要。
特别是观察实体谓词接受数据库值作为参数,因此它们实际上可以强制执行跨越多个实体的不变量。
默认情况下,Datomic 仅对可写入的数据实施一组非常有限的约束,主要包括:
- 唯一性约束:参见Identity and Uniqueness
- 类型约束,例如,您不能将数字写入
:db.type/string
的属性
- 实体解析: 如果
[:my.app/id "fjdsklfjsl"]
查找引用未解析为现有实体 ,[:db/add [:my.app/id "fjdsklfjsl"] :my.app/content 42]
等操作将失败
- 冲突,例如 Datomic 不会让你
:db/add
如果属性基数是一个,则同一实体属性对有 2 个不同的值。
(我可能忘记了一些,如果有请评论。)
特别是在撰写本文时,没有内置方法可以向给定属性添加自定义验证或 'foreign-key' 约束。
然而,组合 Transaction Functions and speculative writes (a.k.a db.with()
) 为您提供了一种强制执行任意不变量的强大方法。例如,您可以将事务包装在事务函数中,该函数使用 db.with()
推测性地应用事务,然后搜索推测性结果以查找不变违规,如果发现一些则抛出异常。您甚至可以通过在 Datalog.
中实现 'search invariant violations' 部分来使此交易功能非常通用
下面是 API 的示例:
[:myapp.fns/checking-invariants
;; a description of the invariant
{:query
[:find ?message ?user-id
:in $db-before $db-after ?tx-data ?tempids ?user-id
:where
[$db-before ?user :myapp.user/id ?user-id]
[$db-before ?user :myapp.user/email ?email-before]
[$db-after ?user :myapp.user/email ?email-after]
[(not= ?email-before ?email-after)]
[(ground "A user may not change her email") ?message]]
:inputs ["user-id-12342141"]}
;; the wrapped transaction
[[:db/add 125315815291 :myapp.user/email "hello.world@yopmail.com"]
[:db/add 125315815291 :myapp.user/name "Foo Bar"]]]
这是 :myapp.fns/checking-invariants
的(未经测试的)实现:
{:db/ident :myapp.fns/checking-invariants,
:db/fn #db/fn{:lang :clojure,
:imports [],
:requires [[datomic.api :as d]],
:params [db invariant-q tx],
:code
(let [{:keys [query inputs]} invariants-q
{:keys [db-before db-after tx-data tempids]}
(d/with db tx)]
(when-some [violations (apply d/q query
db-before db-after tx-data tempids
inputs)]
(throw (ex-info
"Transaction would violate invariants."
{:tx tx
:violations violations
:t (d/basis-t db-before)})))
tx)}}
限制:
- 您只能在外部进行保护:客户必须选择使用此不变检查交易功能。
- 注意性能 - 滥用这种方法可能会给 Transactor 带来过多的负载。在这样做安全的情况下,您可能更愿意使用
db.invoke()
在 Peer 上执行验证
- 确保你的交易是确定性的,因为它将 运行 两次(更准确地说,确保你的交易是否违反不变量是确定性的)
详细来说,大多数关系数据库都有数据库约束的概念。这是 Postgres documentation on constraints。 Datomic 提供哪些工具来约束数据或维护存储数据的一些不变量集?
一种方法是使用修改数据的事务函数,并在修改期间进行约束验证:http://docs.datomic.com/database-functions.html#uses-for-transaction-functions
编辑 2019-06-28: 自 0.9.5927
(Datomic On-Prem)/480-8770
(Datomic Cloud)以来,Datomic 支持更精细的写入-通过 Attribute Predicates, Entity Specs and Entity Predicates 进行时间验证。这使得大部分初始答案无效或无关紧要。
特别是观察实体谓词接受数据库值作为参数,因此它们实际上可以强制执行跨越多个实体的不变量。
默认情况下,Datomic 仅对可写入的数据实施一组非常有限的约束,主要包括:
- 唯一性约束:参见Identity and Uniqueness
- 类型约束,例如,您不能将数字写入
:db.type/string
的属性
- 实体解析: 如果
[:my.app/id "fjdsklfjsl"]
查找引用未解析为现有实体 , - 冲突,例如 Datomic 不会让你
:db/add
如果属性基数是一个,则同一实体属性对有 2 个不同的值。
[:db/add [:my.app/id "fjdsklfjsl"] :my.app/content 42]
等操作将失败
(我可能忘记了一些,如果有请评论。)
特别是在撰写本文时,没有内置方法可以向给定属性添加自定义验证或 'foreign-key' 约束。
然而,组合 Transaction Functions and speculative writes (a.k.a db.with()
) 为您提供了一种强制执行任意不变量的强大方法。例如,您可以将事务包装在事务函数中,该函数使用 db.with()
推测性地应用事务,然后搜索推测性结果以查找不变违规,如果发现一些则抛出异常。您甚至可以通过在 Datalog.
下面是 API 的示例:
[:myapp.fns/checking-invariants
;; a description of the invariant
{:query
[:find ?message ?user-id
:in $db-before $db-after ?tx-data ?tempids ?user-id
:where
[$db-before ?user :myapp.user/id ?user-id]
[$db-before ?user :myapp.user/email ?email-before]
[$db-after ?user :myapp.user/email ?email-after]
[(not= ?email-before ?email-after)]
[(ground "A user may not change her email") ?message]]
:inputs ["user-id-12342141"]}
;; the wrapped transaction
[[:db/add 125315815291 :myapp.user/email "hello.world@yopmail.com"]
[:db/add 125315815291 :myapp.user/name "Foo Bar"]]]
这是 :myapp.fns/checking-invariants
的(未经测试的)实现:
{:db/ident :myapp.fns/checking-invariants,
:db/fn #db/fn{:lang :clojure,
:imports [],
:requires [[datomic.api :as d]],
:params [db invariant-q tx],
:code
(let [{:keys [query inputs]} invariants-q
{:keys [db-before db-after tx-data tempids]}
(d/with db tx)]
(when-some [violations (apply d/q query
db-before db-after tx-data tempids
inputs)]
(throw (ex-info
"Transaction would violate invariants."
{:tx tx
:violations violations
:t (d/basis-t db-before)})))
tx)}}
限制:
- 您只能在外部进行保护:客户必须选择使用此不变检查交易功能。
- 注意性能 - 滥用这种方法可能会给 Transactor 带来过多的负载。在这样做安全的情况下,您可能更愿意使用
db.invoke()
在 Peer 上执行验证
- 确保你的交易是确定性的,因为它将 运行 两次(更准确地说,确保你的交易是否违反不变量是确定性的)