【发布时间】:2015-07-14 20:12:35
【问题描述】:
我开始开发一个 Datomic 支持的 Clojure 应用程序,我想知道声明架构的最佳方式是什么,以解决以下问题:
- 具有简明易读的架构表示
- 在运行我的应用的新版本之前,确保架构已安装并且是最新的。
直观地说,我的方法如下:
- 声明一些辅助函数以使架构声明比使用原始映射更简洁
- 在应用初始化过程中自动安装架构(我还没有足够的知识知道这是否总是有效)。
这是最好的方法吗?人们通常是怎么做的?
【问题讨论】:
我开始开发一个 Datomic 支持的 Clojure 应用程序,我想知道声明架构的最佳方式是什么,以解决以下问题:
直观地说,我的方法如下:
这是最好的方法吗?人们通常是怎么做的?
【问题讨论】:
我为此使用 Conformity,请参阅 Conformity repository。 Yeller Here 也有一篇非常有用的博文,它将指导您如何使用 Conformity。
【讨论】:
原始地图很冗长,但比使用一些 高级 api 有一些很大的优势:
spec API。出于这些原因,我使用原始地图。
这个我也不做。
通常,当您对架构进行更改时,可能会发生很多事情:
您可能需要以某种不明显且不通用的方式更改现有数据,此过程可能需要一些时间。
我 do use some automatization 应用了架构列表和架构更改,但始终处于受控的“部署”阶段,因为可能会发生更多关于数据更新的事情。
假设您有 users.schema.edn 和 roles.schema.edn 文件:
(require '[datomic-manage.core :as manager])
(manager/create uri)
(manager/migrate uri [:users.schema
:roles.schema])
【讨论】:
对于#1,datomic-schema 可能会有所帮助。我没用过,但the example 看起来很有希望。
【讨论】:
我的偏好(作为图书馆的作者,我有偏见)在于datomic-schema - 它只专注于转换为正常的 datomic 模式 - 从那里,您可以像往常一样处理模式。
我希望使用相同的数据来计算实时 datomic 实例和定义之间的架构迁移 - 以便更改枚举、类型和基数以符合您的定义。
datomic-schema 的重要部分(对我而言)是退出路径非常干净 - 如果您发现它不支持某些东西(无论出于何种原因我无法实现),您可以转储您的架构为普通 edn,将其保存并删除依赖项。
如果您想进行某种数据迁移或更具体的迁移(清理数据或先重命名为其他内容),Conformity 将非常有用。
【讨论】:
建议:使用事务函数使声明架构属性更简洁在 EDN 中,这保留了在 EDN 中声明架构的好处,如 @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]]
【讨论】:
我建议使用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 } ))
享受吧! 艾伦
【讨论】: