如何防止事务违反Datomic中的应用程序不变量

Dre*_*lee 7 database transactions clojure datomic

详细说来,大多数关系数据库都有数据库约束的概念.这是关于约束的Postgres 文档.Datomic提供哪些工具来约束数据或维护存储数据的一些不变量?

Val*_*nck 4

编辑 2019-06-28:0.9.5927(Datomic On-Prem) / (Datomic Cloud) 以来,Datomic 通过Attribute PredicatesEntity SpecsEntity Predicates480-8770支持更精细的写入时验证。这使得大部分最初的答案无效或无关紧要。

特别是,观察实体谓词接受数据库值作为参数,因此它们实际上可以强制跨越多个实体的不变量。


默认情况下,Datomic 仅对可写入的数据强制执行一组非常有限的约束,其中主要包括:

  • 唯一性约束:参见身份和唯一性
  • 类型约束,例如,您不能将数字写入以下属性:db.type/string
  • 实体解析:如果查找引用未解析为现有实体,则此类操作[:db/add [:my.app/id "fjdsklfjsl"] :my.app/content 42]将失败[:my.app/id "fjdsklfjsl"]
  • 冲突,例如,如果属性基数为 1,则 Datomic 不会让:db/add同一实体属性对有 2 个不同的值。

(可能忘记了一些,如有请评论。)

特别是在撰写本文时,没有内置方法可以向给定属性添加自定义验证或“外键”约束。

然而,结合事务函数推测写入(又名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"]]]
Run Code Online (Sandbox Code Playgroud)

这是(未经测试的)实现: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)}}
Run Code Online (Sandbox Code Playgroud)

限制:

  • 您只能从外部进行保护:客户端必须选择使用这种不变检查交易功能。
  • 请注意性能 - 滥用此方法可能会给 Transactor 带来过多的负载。在安全的情况下,您可能更愿意使用以下方式对 Peer 进行验证db.invoke()
  • 确保您的交易是确定性的,因为它将运行两次(更准确地说,确保您的交易是否违反不变量是确定性的)