Рекомендуемый способ объявления схемы Datomic в приложении Clojure
Я начинаю разрабатывать приложение Clojure на основе Datomic, и мне интересно, как лучше объявить схему, чтобы решить следующие проблемы:
- Имея краткое, читаемое представление для схемы
- Убедитесь, что схема установлена и обновлена до запуска новой версии моего приложения.
Интуитивно, мой подход будет следующим:
- Объявление некоторых вспомогательных функций делает декларации схемы менее подробными, чем с необработанными картами
- Автоматическая установка схемы как часть инициализации приложения (я еще недостаточно осведомлен, чтобы знать, всегда ли это работает).
Это лучший способ пойти? Как люди обычно делают это?
6 ответов
Необработанные карты многословны, но имеют некоторые большие преимущества по сравнению с некоторыми высокоуровневыми API:
- Схема определена в форме транзакции, то, что вы указываете, является транзакцией (при условии, что слово существует)
- Ваша схема не привязана к конкретной библиотеке или спецификации версии, она всегда будет работать.
- Ваша схема сериализуема (edn) без вызова
spec
API. - Таким образом, вы можете легче хранить и развертывать свою схему в распределенной среде, поскольку она находится в форме данных, а не в форме кода.
По этим причинам я использую сырые карты.
- Автоматическая установка схемы.
Этого я тоже не делаю.
Обычно, когда вы вносите изменения в свою схему, может происходить много вещей:
- Добавить новый атрибут
- Изменить существующий тип атрибута
- Создать полный текст для атрибута
- Создать новый атрибут из других значений
- другие
Что может потребовать от вас изменения существующих данных некоторым неочевидным и не общим способом, в процессе, который может занять некоторое время.
Я использую некоторую автоматизацию для применения списка схем и изменений схемы, но всегда на контролируемой стадии "развертывания", когда может происходить больше вещей, касающихся обновления данных.
Если у вас есть 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, сохраните его и удалите зависимость.
Соответствие будет полезно помимо этого, если вы хотите выполнить какую-либо миграцию данных или более конкретные миграции (очистка данных или переименование вначале во что-то другое).