tolitius / mount

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

How to defstate with configuration #115

Closed introom closed 4 years ago

introom commented 4 years ago

How do you use configuration as a =defstate=.

The obstacle is how to pass the config state as arguments to other states in the system?

If we use =mount/start-with-args=, where the config being the args, the problem is, at the time of =mount/start-with-args= being called, the config state has not been started.

tolitius commented 4 years ago

check out hubble, it has an example of config as a defstate.

the idea is: Clojure compiler will compile it first and then other states may just take it in:

(:require [hubble.env :as env])
;; ...
(defstate http-server 
          :start (start-www (env/config :hubble))
          :stop ((:stop-server http-server)))

i.e. since Clojure compiler sees that config is used in http server, it will compile the hubble.env namespace first.

introom commented 4 years ago

This approach is also how luminus uses to pass around config.

However, one severe defect is that, we cannot modularize the namespace since it explicitly imports that specific config.

This adds complexity to reasoning, reusability, and testing.

We need some way of passing the config as parameters to that namespace to achieve inversion of control.

tolitius commented 4 years ago

can you provide a concrete example of what it is you are trying to do?

inversion of control is definitely not something mount tries to achieve since it is not a framework. "frameworks" are rarely needed in Clojure due to the fact that the focus is on using functions to move data through time rather than hierarchy of nouns working on the same piece of state.

adds complexity needs examples, and perhaps things could be done/designed in a different way to remove the anticipated complexity.

introom commented 4 years ago

can you provide a concrete example of what it is you are trying to do?

I have two namespaces:

(ns app.config)
(defstate config ,,,)
(ns app.server)
(defstate server ,,,
    which internally uses app.config)

My problem is, How to let app.server/server use the config from ns app.config, without explicitly (require '(app.config)).

The problem of explicitly requring ns app.config is that doing so will closely couple (ns app.server) with (ns app.config).

One plausible way is to pass app.config/config as argument to app.server/server. But how to achieve that?

tolitius commented 4 years ago

why not have a server depend on a config that it always needs in order to start?

you can always swap this config with a test config or any other data structure via (mount/start-with ...).

here is a more complete example on how to do it.

introom commented 4 years ago

Yes. I am aware of such pattern.

So, IIUC, the ideology when using mount is: A module requires defstate of another module by explicit requiring the namespace. During testing, we swap the definition of that defstate with (mout/start-with).

What I am wondering is: Is it possible that, A module uses the state of another module by argument passing instead of the above explicit importing of entities from another clojure namespace.

tolitius commented 4 years ago

A module uses the state of another module by argument passing instead of the above explicit importing of entities from another clojure namespace.

in order for a state to take arguments, something needs to call this state passing the arguments. mount does not really call states, a Clojure compiler does. there is a way to pass runtime arguments, but in case of an application configuration, I believe it is best to "pass" it in as another state. in tests you could start the application with a test configuration, hence mount/start-with.

I would still ask a question:

why not have a server depend on a config that it always needs in order to start?

introom commented 4 years ago

I can close the issue for now as I've understood mount's philosophy of globally swappable state .

tolitius commented 4 years ago

sure, feel free to reopen in case you find the answer to the last question above

introom commented 4 years ago

The answer is apparent as global variable (either immutable or not) vs local variable by parameter passing.

https://clojurians-log.clojureverse.org/clojure/2020-02-12/1581487563.428900

tolitius commented 4 years ago

ah, k. I thought you had a concrete reason why this won't work for you, and I can help you. I didn't realize this is rather based on just a "this is bad" opinion.

now as I understand where you are coming from the issue may really be closed.