clojure hive-mind, i want you opinion on something. Assume I have a schema.clj
which contains my datomic schema (the usual). Every time I start the application (in development and in production) this schema is transacted so whatever has been added gets correctly transacted too. Now, some time ago I added a new attribute {:db/ident :foo/bar ...}
, now after a few weeks turns out that I want to rename this attribute like :alice/bob
. Following the documentation of datomic i'm supposed to {:db/id :foo/bar :db/ident :alice/bob}
, but that clearly doesn't work in development as :foo/bar
isn't yet defined when i start my system (it's in the same transaction) but would work on a running system with the attribute already installed. How do you handle these cases?
we have a schema.edn that contains migrations, each migration has it's own :db/ident
the startup code runs only new migrations whose ident isn't in the db yet
so migrations is just a list of txs to run in order... or a fully qualified symbol denoting a function to call (connection given as argument)
separate txs help with that and I like that we can see the schema evolution from the schema file as well
We do the same. This:
[{:db/ident :tx/id
:db/cardinality :db.cardinality/one
:db/valueType :db.type/keyword
:db/unique :db.unique/value}
{:db/ident :tx/status
:db/valueType :db.type/ref
:db/cardinality :db.cardinality/one}
{:db/ident :tx.status/applied}]
is always transacted on startup. Everything else looks like:
{:tx-id :migration-0001
:tx-data
[{:db/ident :editor-session/pid
:db/cardinality :db.cardinality/one
:db/valueType :db.type/string
:db/unique :db.unique/value}]}
Or:
{:tx-id :migration-0002
:tx-kind :fn
:tx-data
server.db.migrations/somefn}
These are kept as resources which are transacted by:
(defn tx-resource!
[conn resource]
(tx! conn (resources/read-resource resource)))
(defn- tx-status
[conn tx-id]
(-> (conn->db conn)
(q-by-ident [:tx/id tx-id] [{:tx/status [:db/ident]}])
:tx/status
:db/ident))
(defn- tx-apply!
[conn {:keys [tx-id tx-data]}]
(tx! conn (conj tx-data {:tx/id tx-id :tx/status
:tx.status/applied})))
(defn- tx-applied?
[conn tx-id]
(case (tx-status conn tx-id)
:tx.status/applied true
nil))
(defn tx-idempotent!
[conn resource]
(let [{:keys [tx-id tx-data tx-kind] :as props} (resources/read-resource resource)]
(when-not (tx-applied? conn tx-id)
(case tx-kind
:fn (do
(require (symbol (namespace tx-data)))
(tx-apply! conn (assoc props :tx-data ((resolve tx-data) conn))))
(tx-apply! conn props)))))
thank you for your advice 🙂
I wrote most of this before I discovered https://github.com/magnetcoop/stork which is pretty similar. I borrowed its approach to supporting migration functions