2015-07-14 13 views
8

sto iniziando a sviluppare un app Clojure Datomic-backed, e mi chiedo qual è il modo migliore per dichiarare lo schema, al fine di affrontare i seguenti problemi:modo consigliato per dichiarare lo schema Datomic in applicazione Clojure

  1. Avere una rappresentazione concisa e leggibile per lo schema
  2. Assicurarsi che lo schema sia installato e aggiornato prima di eseguire una nuova versione della mia app.

Intuitivamente, il mio approccio sarebbe il seguente:

  1. Dichiarare alcune funzioni di supporto per fare dichiarazioni di schema meno prolissa che con le mappe prime
  2. installare automaticamente lo schema come parte della inizializzazione del app (non sono ancora abbastanza informata per sapere se funziona sempre).

È questo il modo migliore per andare? Come fanno le persone di solito a farlo?

risposta

4

I Uso di conformità per questo vedere Conformity repository. C'è anche un blogpost molto utile da Yeller Here che ti guiderà su come usare Conformity.

+0

grazie! Questo sembra risolvere la preoccupazione n. 2. Hai qualche consiglio su # 1? –

2

Per il numero 1, datomic-schema potrebbe essere di aiuto. Non l'ho usato, ma the example sembra promettente.

+0

Ora voglio convalidare entrambe le risposte.Ho avuto il suo arrivo, dal momento che la mia domanda riguardava 2 dubbi :) –

1

Si consiglia di utilizzare Tupelo Datomic per iniziare. Ho scritto questa libreria per semplificare la creazione dello schema Datomic e facilitare la comprensione, proprio come alludi alla tua domanda.

Ad esempio, supponiamo di voler tenere traccia delle informazioni per l'agenzia di spionaggio mondiale. Creiamo alcuni attributi che si applicano ai nostri eroi & furfanti (vedere il codice eseguibile 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 

Per la: attributo di arma/tipo, vogliamo usare un tipo enumerato in quanto ci sono solo un numero limitato di scelte a disposizione dei nostri antagonisti:

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

Creiamo alcuni antagonisti e carico li nel DB. Nota che stiamo usando solo valori Clojure e letterali semplici, e non dobbiamo preoccuparci di alcuna conversione Datomic specifica.

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

Divertiti! Alan

3
  1. mappe prime sono prolisso, ma hanno alcuni grandi vantaggi rispetto all'uso di un certo alto livello api:

    • schema è definito in forma di transazione, ciò che si specifica è transactable (supponendo esiste la parola)
    • Lo schema non è legato a una particolare libreria o versione specifica, funzionerà sempre.
    • Lo schema è serializzabile (edn) senza chiamare un'API spec.
    • Così è possibile archiviare e distribuire gli schemi più facilmente in un ambiente distribuito poiché è in forma di dati e non in forma di codice.

Per queste ragioni io uso le mappe prime.

  1. Installazione automatica dello schema.

Questo non lo faccio neanche io.

Di solito quando si apporta una modifica allo schema molte cose possono accadere:

  • Aggiungi nuovo attributo
  • Cambia tipo attributo esistente
  • creare full-text per un attributo
  • Crea nuovo attributo da altri valori
  • Altri

Potrebbe essere necessario modificare i dati esistenti in un modo non ovvio e non generico, in un processo che potrebbe richiedere del tempo.

I do use some automatization per l'applicazione di un elenco di schemi e modifiche dello schema, ma sempre in una fase di "distribuzione" controllata quando possono verificarsi più cose riguardanti l'aggiornamento dei dati.

Supponendo di avere users.schema.edn e roles.schema.edn file:

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

Mi sembra che alcuni aiutanti per la definizione di uno schema siano innocui, lo vedrei solo come una notazione * più pratica * di letterali di mappa nel caso in cui la tua lingua sia Clojure . Finché sono ancora dati, credo che sia tutto a posto. –

+1

Sì, il problema dell'utilizzo di un helper è che lo schema smette di essere dati e inizia ad essere una "api call", la distinzione è importante se si considera che i dati siano già in forma transact. –

+0

Non sono davvero d'accordo con questa affermazione. Per me una funzione che emette dati è tanto orientata ai dati e trasportabile quanto i letterali della struttura dei dati Clojure. L'unica differenza che posso pensare è che è meno agonistico della lingua. –

1

La mia preferenza (e io sono di parte, come l'autore della biblioteca) si trova con datomic-schema - Si concentra sul solo facendo la trasformazione alla normalità schema datomic - da lì, lo schema viene trattato come faresti normalmente.

Sto cercando di utilizzare gli stessi dati per calcolare la migrazione dello schema tra l'istanza datomica attiva e le definizioni, in modo che l'enumerazione, i tipi e la cardinalità vengano modificati per conformarsi alla definizione.

La parte importante (per me) di datomic-schema è che il percorso di uscita è molto pulito - Se trovi che non supporta qualcosa (che non posso implementare per qualsiasi motivo) lungo la linea, puoi scaricare il tuo schema come edn semplice, salvarlo e rimuovere la dipendenza.

Conformity sarà utile oltre che se si desidera eseguire una sorta di migrazione dei dati o migrazioni più specifiche (ripulire i dati o rinominare prima qualcos'altro).

1

Proposta: utilizzando le funzioni di transazione per fare dichiarare schema attribuisce meno prolisso in EDN, questo conservando i vantaggi di dichiarare lo schema in EDN come dimostrato da @Guillermo Winkler's answer.

Esempio:

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

Questo è davvero un approccio "out of the box"! Qualsiasi pro, contro? – onetom

+0

onetom Non l'ho ancora provato, finora sono contento di usare una DSL nel mio codice applicazione. –

Problemi correlati