Рекомендуемый способ объявления схемы Datomic в приложении Clojure

Я начинаю разрабатывать приложение Clojure на основе Datomic, и мне интересно, как лучше объявить схему, чтобы решить следующие проблемы:

  1. Имея краткое, читаемое представление для схемы
  2. Убедитесь, что схема установлена ​​и обновлена ​​до запуска новой версии моего приложения.

Интуитивно, мой подход будет следующим:

  1. Объявление некоторых вспомогательных функций делает декларации схемы менее подробными, чем с необработанными картами
  2. Автоматическая установка схемы как часть инициализации приложения (я еще недостаточно осведомлен, чтобы знать, всегда ли это работает).

Это лучший способ пойти? Как люди обычно делают это?

6 ответов

  1. Необработанные карты многословны, но имеют некоторые большие преимущества по сравнению с некоторыми высокоуровневыми API:

    • Схема определена в форме транзакции, то, что вы указываете, является транзакцией (при условии, что слово существует)
    • Ваша схема не привязана к конкретной библиотеке или спецификации версии, она всегда будет работать.
    • Ваша схема сериализуема (edn) без вызова spec API.
    • Таким образом, вы можете легче хранить и развертывать свою схему в распределенной среде, поскольку она находится в форме данных, а не в форме кода.

По этим причинам я использую сырые карты.

  1. Автоматическая установка схемы.

Этого я тоже не делаю.

Обычно, когда вы вносите изменения в свою схему, может происходить много вещей:

  • Добавить новый атрибут
  • Изменить существующий тип атрибута
  • Создать полный текст для атрибута
  • Создать новый атрибут из других значений
  • другие

Что может потребовать от вас изменения существующих данных некоторым неочевидным и не общим способом, в процессе, который может занять некоторое время.

Я использую некоторую автоматизацию для применения списка схем и изменений схемы, но всегда на контролируемой стадии "развертывания", когда может происходить больше вещей, касающихся обновления данных.

Если у вас есть users.schema.edn а также roles.schema.edn файлы:

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

Я использую Conformity для этого см. Хранилище Conformity. Здесь также есть очень полезный пост от Yeller, в котором вы узнаете, как использовать Conformity.

Для #1 может пригодиться datomic-схема. Я не использовал его, но пример выглядит многообещающим.

Я бы предложил использовать Tupelo Datomic, чтобы начать. Я написал эту библиотеку, чтобы упростить создание Datomic-схемы и облегчить понимание, как вы и упоминали в своем вопросе.

В качестве примера, предположим, мы пытаемся отслеживать информацию для мирового шпионского агентства. Давайте создадим несколько атрибутов, которые будут применяться к нашим героям и злодеям (см. Исполняемый код в модульном тесте).

  (: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

Для атрибута: arms /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                 } ))

Наслаждайтесь! Алан

Предложение: использование функций транзакции, чтобы сделать декларацию атрибутов схемы менее детальной в EDN, это сохраняет преимущества объявления вашей схемы в EDN, что продемонстрировано в ответе @Guillermo Winkler.

Пример:

;; 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]]

Мои предпочтения (и я предвзятый, как автора библиотеки) связаны с datomic-схемой - она ​​фокусируется только на преобразовании в обычную двухатомную схему - оттуда вы выполняете схему, как обычно.

Я рассчитываю использовать одни и те же данные для расчета миграции схемы между действующим экземпляром данных и определениями - так, чтобы перечисления, типы и количество элементов были изменены в соответствии с вашим определением.

Важной частью (для меня) datomic-схемы является то, что путь выхода очень чистый - если вы обнаружите, что он не поддерживает что-то (что я не могу реализовать по какой-либо причине) в дальнейшем, вы можете вывести свою схему как обычный edn, сохраните его и удалите зависимость.

Соответствие будет полезно помимо этого, если вы хотите выполнить какую-либо миграцию данных или более конкретные миграции (очистка данных или переименование вначале во что-то другое).

Другие вопросы по тегам