Fork me on GitHub
#datomic
<
2021-06-24
>
helios08:06:18

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?

tatut08:06:45

we have a schema.edn that contains migrations, each migration has it's own :db/ident

tatut08:06:04

the startup code runs only new migrations whose ident isn't in the db yet

tatut08:06:44

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)

tatut08:06:06

separate txs help with that and I like that we can see the schema evolution from the schema file as well

tvaughan14:06:30

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

helios14:06:51

thank you for your advice 🙂

tvaughan14:06:17

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