【问题标题】:How to prevent transactions from violating application invariants in Datomic如何防止事务违反 Datomic 中的应用程序不变量
【发布时间】:2018-01-15 18:37:04
【问题描述】:

详细地说,大多数关系数据库都有数据库约束的概念。这是 Postgres documentation on constraints。 Datomic 提供了哪些工具来约束数据或在存储的数据上维护一组不变量?

【问题讨论】:

    标签: database clojure transactions datomic


    【解决方案1】:

    编辑 2019-06-28: 由于 0.9.5927(Datomic On-Prem)/480-8770(Datomic Cloud),Datomic 通过 Attribute Predicates、@987654322 支持更精细的写入时间验证@ 和 Entity Predicates。这使得大部分初始答案无效或不相关。

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


    默认情况下,Datomic 仅对可以写入的数据实施非常有限的一组约束,主要包括:

    • 唯一性约束:Identity and Uniqueness
    • 类型约束,例如,您不能将数字写入 :db.type/string 的属性
    • 实体解析:如果[:my.app/id "fjdsklfjsl"]lookup-ref 未解析为现有实体,则[:db/add [:my.app/id "fjdsklfjsl"] :my.app/content 42] 之类的操作将失败
    • 冲突,例如,如果属性基数为 1,Datomic 不会让您:db/add 2 个不同的值用于同一实体-属性对。

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

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

    但是,将Transaction Functionsspeculative 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() 在对等节点上执行验证
    • 确保您的事务是确定性的,因为它将运行两次(更准确地说,确保您的事务是否违反不变量是确定性的)

    【讨论】:

      【解决方案2】:

      一种方法是使用修改数据并在修改期间进行约束验证的事务函数:http://docs.datomic.com/database-functions.html#uses-for-transaction-functions

      【讨论】:

        猜你喜欢
        • 2014-06-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-09-28
        • 2019-07-02
        • 1970-01-01
        • 2016-02-15
        • 1970-01-01
        相关资源
        最近更新 更多