juxt / clip

Light structure and support for dependency injection
MIT License
228 stars 15 forks source link

Allow more general references with `clip/ref` #14

Closed dpetranek closed 4 years ago

dpetranek commented 4 years ago

I have a system where I want to reference a single piece of config from multiple places (I'm using aero for config) (and it's wonderful! :heart:):

{
 :components
 {:paragon/config
  {:start
   {:db-uri #profile {:dev {:backend :file :path "resources/db/main-dev" }
                      :test {:backend :file :path "resources/db/main-test" }
                      :prod {:backend :file :path "resources/db/main" }}}}

  :paragon/db
  {:pre-start (paragon.services.datahike/initialize-db (:db-uri (clip/ref :paragon/config)))
   :start (paragon.services.datahike/connect-db (:db-uri (clip/ref :paragon/config)))}

  :paragon/http
  {:start (paragon.services.http/start-server #or [#env PORT 3000])
   :stop (paragon.services.http/stop-server this)}}
 }

In the above example, I didn't want to duplicate the :db-uri config twice for my :paragon/db :pre-start and :start. I finally figured out that I needed to nest my static config under [:component <whatever-my-config-key-is> :start] in order to reference it with clip/ref.

I don't really think of my config as a component, and it feels weird to have to put it under a :start key. Is there a better way to handle this situation?

SevereOverfl0w commented 4 years ago

You have 2 options for solving this, neither require any changes to clip! (this is the benefit of relying on Aero)

Option 1, make your clip system a sub-key:

{:db-uri #profile {:dev {:backend :file …} :prod {:backend …}}
 :juxt.clip/system
 {:components
  {:paragon/db
   {:pre-start (paragon.foo/initialize-db #ref [:db-uri])
    :start (connect-db #ref [:db-uri])}}}}

Then when you read your aero config, you instead do (:juxt.clip/system (aero/read-config …)).

Option 2, embed your config under a namespaced key. The distinction is that your system definition carries the config used to create it along with it. This means you have some information about how the system was created, but I've not yet thought up a reason to do this.

{:components {:paragon/db {:pre-start (initialize-db #ref [:com.paragon/config :db-uri])}}
 :com.paragon/config {:db-uri #profile {…}}}

I've used Option 1 to great success, and there's less typing to reference other keys as you don't have a shared top-level key to go through.

I hope this helps!