tolitius / mount

managing Clojure and ClojureScript app state since (reset)
Eclipse Public License 1.0
1.22k stars 88 forks source link

Support starting an arbitrary sequence #116

Closed yatesco closed 4 years ago

yatesco commented 4 years ago

(I know what the right answer is, which is to declare compile time dependencies on all pre-requisites but this is sometimes impractical ;-)).

Prior to mount I used a blunt approach:

(defn start-system [db] 
  (let [thing-1 (thing-1/create db)
         thing-2 (thing-2/create db thing-1)
         ...]
      {:thing-1 thing-1 ....}))

this worked well for a few hundred components and had the very nice property that the sequence of boot up was clear and always correct (as the compiler forced dependencies to be defined before they were used).

However, then I needed to patch some of the data structures in the DB before the rest of the system started up, so they trivially went at the top of the let block:

(defn start-system [db] 
  (let [_ (patch-1 ...)
         _ (patch-2 ...)
thing-1 (thing-1/create db)
         thing-2 (thing-2/create db thing-1)
         ...]
      {:thing-1 thing-1 ....}))

thing-1 didn't have any code API dependency on the patch but they did assume the patches had run and therefore expected the new patched shapes out of the DB.

Equally, there are some bootstrap activities (setting default JVM to Timezone to UTC etc.) which again, all went in the appropriate sequence in start-system.

My assertion is that it is impractical for each component to add a compile dependency on every single thing it assumes has happened (i.e. the bootstrap and patches).

It is also more complex as some of those patches (for example) actually use thing-Xs.

I have since introduced mount and changed the start-system to:

(defn start-system [db] 
  (let [_ (bootstrap ....)
         _ (patch-1 ....)
         _ (patch-2 ....)
         thing-1 thing-1/component 
         thing-2 thing-2/component
         ...]))

(where component is (defstate component... in the component's ns)

but thing-X is starting before the bootstrap and patches.

It would be great if it was possible to annotate defstate to indicate "start this as early as possible" which could be optionally parameterised with a numerical sequence. Then I could:

Another approach I considered, which doesn't solve the problem but does make it more digestible, would be to have coarser grained subsystems, so:

I dunno :-) - just reporting a real use-case I ran into which I don't have the brainpower to see my way through with mount :-).

yatesco commented 4 years ago

Actually, is the answer simply to change the order of requires? It never occurred to me that the order would be honoured....assumptions and all that :-)

tolitius commented 4 years ago

yea, in the namespace (where the "system" is created/started) mount will start components based on the order compiler sees them. for example:

(ns app
  (:require [foo.bar :as fbar]
            [foo.baz :as fbaz]))

(defn start-system []
  ;; ...
)

components in fbar will start first.

unless fbar also requires fbaz :)

yatesco commented 4 years ago

Thanks all - closing this. TIL that the order of requires matters :-)