2015-07-14 19 views
8

Zaczynam rozwijać Datomic-backed aplikację Clojure, i zastanawiam się, co jest najlepszym sposobem, aby zadeklarować schemat, w celu rozwiązania następujących problemów:Zalecany sposób deklarowania Datomic schematu w aplikacji Clojure

  1. Posiadanie zwięzłej, czytelnej reprezentacji schematu
  2. Zapewnienie, że schemat jest zainstalowany i aktualny przed uruchomieniem nowej wersji mojej aplikacji.

Intuicyjnie, moje podejście byłoby następujące:

  1. Deklarowanie kilka funkcji pomocniczych, aby deklaracje schematu mniej gadatliwy niż z surowych map
  2. Automatyczne instalowanie schematu jako część inicjalizacji app (nie mam jeszcze wystarczającej wiedzy, aby wiedzieć, czy to zawsze działa).

Czy to najlepszy sposób, aby przejść? Jak ludzie zwykle to robią?

Odpowiedz

4

Używam zgodności dla tego zobacz Conformity repository. Istnieje również bardzo przydatny blog z Yeller Here, który poprowadzi Cię, jak korzystać z Conformity.

+0

dzięki! To wydaje się rozwiązać problem # 2. Czy masz jakieś porady na temat numeru 1? –

2

Dla # 1, datomic-schema może być pomocne. Nie użyłem tego, ale wygląda na obiecujące.

+0

Teraz chcę zweryfikować obie odpowiedzi.Nadchodziło, bo moje pytanie dotyczyło dwóch problemów :) –

1

Proponuję użyć Tupelo Datomic, aby rozpocząć. Napisałem tę bibliotekę, aby uprościć tworzenie schematów Datomic i ułatwić zrozumienie, podobnie jak wspominasz w swoim pytaniu.

Jako przykład załóżmy, że staramy się śledzić informacje o światowej premierze agencji szpiegowskiej. Stwórzmy kilka atrybutów, które będą miały zastosowanie do naszych bohaterów: & (zobacz kod wykonywalny 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 

Dla: atrybut broń/typ, chcemy wykorzystać typ wyliczany ponieważ istnieje tylko ograniczona liczba wyborów do dyspozycji naszych antagonistów:

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

Stwórzmy kilka antagonistami i obciążenia je do DB. Zwróć uwagę, że używamy tutaj zwykłych wartości i literałów Clojure i nie musimy się martwić o żadne konkretne konwersje 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     })) 

Ciesz się! Alan

3
  1. Surowe mapy są gadatliwe, ale mają jakieś wielkie zalety w stosunku do korzystania z niektórych wysoki poziom API:

    • Schema jest zdefiniowana w formie transakcji, co jest określony transactable (zakładając, słowo istnieje)
    • Twój schemat nie jest związany z konkretną biblioteką lub wersją spec, to zawsze zadziała.
    • Twój schemat jest możliwy do serializacji (edn) bez wywoływania interfejsu API spec.
    • Możesz więc łatwiej przechowywać i wdrażać swój schemat w środowisku rozproszonym, ponieważ jest on w formie danych, a nie w postaci kodu.

Z tych powodów używam surowych map.

  1. Automatyczne instalowanie schematu.

Tego też nie robię.

Zwykle po wprowadzeniu zmiany do schematu wiele rzeczy może się dziać:

  • Dodaj nowy atrybut
  • typ Zmień istniejący atrybut
  • Tworzenie pełny tekst atrybutu
  • Utwórz nowy atrybut z innych wartości
  • Inne

Które mogą wymagać zmiany istniejących danych w sposób nieoczywisty i ogólny, w procesie, który może zająć trochę czasu.

I za zastosowanie listy schematów i zmian schematów, ale zawsze na kontrolowanym etapie "wdrażania", gdy może wystąpić więcej problemów dotyczących aktualizacji danych.

Zakładając masz users.schema.edn i roles.schema.edn pliki:

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

Wydaje mi się, że niektórzy pomocnicy do zdefiniowania schematu są nieszkodliwi, widziałbym to tylko jako bardziej praktyczny * zapis * niż literały mapy w przypadku, gdy twoim językiem jest Clojure . Dopóki jest to nadal dane, uważam, że jest w porządku. –

+1

Tak, problem z używaniem helpera polega na tym, że twój schemat przestaje być danymi i zaczyna być "połączeniem api", rozróżnienie jest ważne, jeśli uważasz, że dane są już w formie transakcyjnej. –

+0

Naprawdę nie zgadzam się z tym stwierdzeniem. Dla mnie funkcja wysyłająca dane jest tak samo zorientowana na dane i możliwa do przeprowadzenia, jak literały struktury danych Clojure. Jedyną różnicą, o której mogę pomyśleć, jest to, że jest mniej agnostyczny. –

1

moje preferencje (i jestem stronniczy, ponieważ autor biblioteki) spoczywa datomic-schema - To tylko robi skupia się na transformację do normalnego schematu datomic - stamtąd dokonuje się transakcji schematu, jak normalnie.

Próbuję użyć tych samych danych do obliczenia migracji schematu między instancją na żywo danych a definicjami - tak, aby wyliczenia, typy i liczności zostały zmienione zgodnie z definicją.

Ważną częścią (dla mnie) datomic-schema jest to, że ścieżka wyjścia jest bardzo czysta - Jeśli okaże się, że nie obsługuje ona czegoś (którego nie mogę wdrożyć z jakiegokolwiek powodu) w dół, możesz zrzucić schema as plain edn, zapisz go i usuń zależność.

Conformity będzie przydatny, jeśli chcesz przeprowadzić migrację danych lub bardziej szczegółowe migracje (czyszczenie danych lub najpierw zmianę nazwy na coś innego).

1

Wniosek: przy użyciu funkcji transakcyjnych do deklarowania atrybutów schematu mniej gadatliwy w EDN, to zachowując korzyści z deklarując swój schemat w EDN jak wykazano @Guillermo Winkler's answer.

Przykład:

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

To jest naprawdę "od razu" podejście! Jakieś plusy, minusy? – onetom

+0

onetom Nie próbowałem tego, do tej pory cieszę się z używania DSL w moim kodzie aplikacji. –

Powiązane problemy