【问题标题】:Recommended way to declare Datomic schema in Clojure application在 Clojure 应用程序中声明 Datomic 模式的推荐方法
【发布时间】:2015-07-14 20:12:35
【问题描述】:

我开始开发一个 Datomic 支持的 Clojure 应用程序,我想知道声明架构的最佳方式是什么,以解决以下问题:

  1. 具有简明易读的架构表示
  2. 在运行我的应用的新版本之前,确保架构已安装并且是最新的。

直观地说,我的方法如下:

  1. 声明一些辅助函数以使架构声明比使用原始映射更简洁
  2. 在应用初始化过程中自动安装架构(我还没有足够的知识知道这是否总是有效)。

这是最好的方法吗?人们通常是怎么做的?

【问题讨论】:

    标签: clojure schema datomic


    【解决方案1】:

    我为此使用 Conformity,请参阅 Conformity repository。 Yeller Here 也有一篇非常有用的博文,它将指导您如何使用 Conformity。

    【讨论】:

    • 谢谢!这似乎解决了第二个问题。你对 #1 有什么建议吗?
    【解决方案2】:
    1. 原始地图很冗长,但比使用一些 高级 api 有一些很大的优势:

      • Schema 以事务形式定义,你指定的是transactable(假设单词存在)
      • 您的架构与特定库或规范版本无关,它始终有效。
      • 您的架构可序列化 (edn),无需调用 spec API。
      • 因此您可以在分布式环境中更轻松地存储和部署架构,因为它是数据形式而不是代码形式。

    出于这些原因,我使用原始地图。

    1. 自动安装架构。

    这个我也不做。

    通常,当您对架构进行更改时,可能会发生很多事情:

    • 添加新属性
    • 更改现有属性类型
    • 为属性创建全文
    • 从其他值创建新属性
    • 其他

    您可能需要以某种不明显且不通用的方式更改现有数据,此过程可能需要一些时间。

    do use some automatization 应用了架构列表和架构更改,但始终处于受控的“部署”阶段,因为可能会发生更多关于数据更新的事情。

    假设您有 users.schema.ednroles.schema.edn 文件:

    (require '[datomic-manage.core :as manager])
    (manager/create uri)
    (manager/migrate uri [:users.schema
                          :roles.schema])
    

    【讨论】:

    • 在我看来,定义模式的一些助手是无害的,如果您的语言是 Clojure,我只会将其视为比映射文字更实用的 表示法。只要它仍然是数据,我相信没关系。
    • 是的,使用帮助器的问题是您的架构不再是数据并开始成为“api 调用”,如果您认为数据已经处于事务形式,则区别很重要。
    • 我不太同意这种说法。对我来说,输出数据的函数与 Clojure 数据结构字面量一样面向数据和可交易。我能想到的唯一区别是它与语言无关。
    • 并非如此,例如,如果您想在分布式环境中部署架构,则需要在获得可序列化数据之前执行您的函数。您的方法假定您的架构将始终采用代码形式,让您的架构采用数据文字形式允许替代存储、序列化和通信。
    • 我认为这可能是事务功能的一个很好的用例。我们可以在不影响便携性的情况下使用它们来降低噪音。
    【解决方案3】:

    对于#1,datomic-schema 可能会有所帮助。我没用过,但the example 看起来很有希望。

    【讨论】:

    • 现在我想验证两个答案。我来了,因为我的问题涉及 2 个问题:)
    【解决方案4】:

    我的偏好(作为图书馆的作者,我有偏见)在于datomic-schema - 它只专注于转换为正常的 datomic 模式 - 从那里,您可以像往常一样处理模式。

    我希望使用相同的数据来计算实时 datomic 实例和定义之间的架构迁移 - 以便更改枚举、类型和基数以符合您的定义。

    datomic-schema 的重要部分(对我而言)是退出路径非常干净 - 如果您发现它不支持某些东西(无论出于何种原因我无法实现),您可以转储您的架构为普通 edn,将其保存并删除依赖项。

    如果您想进行某种数据迁移或更具体的迁移(清理数据或先重命名为其他内容),Conformity 将非常有用。

    【讨论】:

      【解决方案5】:

      建议:使用事务函数使声明架构属性更简洁在 EDN 中,这保留了在 E​​DN 中声明架构的好处,如 @Guillermo Winkler's answer 所示。

      例子:

      ;; defining helper function
      [{:db/id #db/id[:db.part/user]
        :db/doc "Helper function for defining entity fields schema attributes in a concise way."
        :db/ident :utils/field
        :db/fn #db/fn {:lang :clojure
                       :require [datomic.api :as d]
                       :params [_ ident type doc opts]
                       :code [(cond-> {:db/cardinality :db.cardinality/one
                                       :db/fulltext true
                                       :db/index true
                                       :db.install/_attribute :db.part/db
      
                                       :db/id (d/tempid :db.part/db)
                                       :db/ident ident
                                       :db/valueType (condp get type
                                                       #{:db.type/string :string} :db.type/string
                                                       #{:db.type/boolean :boolean} :db.type/boolean
                                                       #{:db.type/long :long} :db.type/long
                                                       #{:db.type/bigint :bigint} :db.type/bigint
                                                       #{:db.type/float :float} :db.type/float
                                                       #{:db.type/double :double} :db.type/double
                                                       #{:db.type/bigdec :bigdec} :db.type/bigdec
                                                       #{:db.type/ref :ref} :db.type/ref
                                                       #{:db.type/instant :instant} :db.type/instant
                                                       #{:db.type/uuid :uuid} :db.type/uuid
                                                       #{:db.type/uri :uri} :db.type/uri
                                                       #{:db.type/bytes :bytes} :db.type/bytes
                                                       type)}
                                      doc (assoc :db/doc doc)
                                      opts (merge opts))]}}]
      
      ;; ... then (in a later transaction) using it to define application model attributes
      [[:utils/field :person/name :string "A person's name" {:db/index true}]
       [:utils/field :person/age :long "A person's name" nil]]
      

      【讨论】:

      • 这真的是一种“开箱即用”的方法!有什么优缺点吗?
      • onetom 我还没有尝试过,到目前为止我很高兴在我的应用程序代码中使用 DSL。
      【解决方案6】:

      我建议使用Tupelo Datomic 开始。我编写这个库是为了简化 Datomic 模式的创建并便于理解,就像您在问题中提到的那样。

      举个例子,假设我们正试图跟踪世界首屈一指的间谍机构的信息。让我们创建一些适用于我们的英雄和反派的属性(参见可执行代码in the unit test)。

        (:require [tupelo.datomic   :as td]
                  [tupelo.schema    :as ts])
      
        ; Create some new attributes. Required args are the attribute name (an optionally namespaced
        ; keyword) and the attribute type (full listing at http://docs.datomic.com/schema.html). We wrap
        ; the new attribute definitions in a transaction and immediately commit them into the DB.
        (td/transact *conn* ;   required              required              zero-or-more
                            ;  <attr name>         <attr value type>       <optional specs ...>
          (td/new-attribute   :person/name         :db.type/string         :db.unique/value)      ; each name      is unique
          (td/new-attribute   :person/secret-id    :db.type/long           :db.unique/value)      ; each secret-id is unique
          (td/new-attribute   :weapon/type         :db.type/ref            :db.cardinality/many)  ; one may have many weapons
          (td/new-attribute   :location            :db.type/string)     ; all default values
          (td/new-attribute   :favorite-weapon     :db.type/keyword ))  ; all default values
      

      对于 :weapon/type 属性,我们希望使用枚举类型,因为我们的对手只有有限数量的选择:

        ; Create some "enum" values. These are degenerate entities that serve the same purpose as an
        ; enumerated value in Java (these entities will never have any attributes). Again, we
        ; wrap our new enum values in a transaction and commit them into the DB.
        (td/transact *conn*
          (td/new-enum :weapon/gun)
          (td/new-enum :weapon/knife)
          (td/new-enum :weapon/guile)
          (td/new-enum :weapon/wit))
      

      让我们创建一些对抗者并将它们加载到数据库中。请注意,我们在这里只使用普通的 Clojure 值和文字,我们不必担心任何 Datomic 特定的转换。

        ; Create some antagonists and load them into the db.  We can specify some of the attribute-value
        ; pairs at the time of creation, and add others later. Note that whenever we are adding multiple
        ; values for an attribute in a single step (e.g. :weapon/type), we must wrap all of the values
        ; in a set. Note that the set implies there can never be duplicate weapons for any one person.
        ; As before, we immediately commit the new entities into the DB.
        (td/transact *conn*
          (td/new-entity { :person/name "James Bond" :location "London"     :weapon/type #{ :weapon/gun :weapon/wit   } } )
          (td/new-entity { :person/name "M"          :location "London"     :weapon/type #{ :weapon/gun :weapon/guile } } )
          (td/new-entity { :person/name "Dr No"      :location "Caribbean"  :weapon/type    :weapon/gun                 } ))
      

      享受吧! 艾伦

      【讨论】:

        猜你喜欢
        • 2020-07-06
        • 1970-01-01
        • 2011-10-23
        • 1970-01-01
        • 2011-07-11
        • 2011-04-17
        • 2019-01-30
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多