duct-framework / duct

Server-side application framework for Clojure
MIT License
1.13k stars 51 forks source link

Data-driven system definition #41

Closed weavejester closed 8 years ago

weavejester commented 8 years ago

Currently the system is created through the component/system-map function:

(defn new-system [config]
  (let [config (meta-merge base-config config)]
    (-> (component/system-map
         :app     (handler-component (:app config))
         :http    (jetty-server (:http config))
         :example (endpoint-component example-endpoint))
        (component/system-using
         {:http [:app]
          :app  [:example]}))))

But instead we could define the system via a data structure:

{:components
 {:app  duct.component.handler/handler-component
  :http ring.component.jetty/jetty-server}
 :endpoints
 {:example bar.endpoint.example/example-endpoint}
 :dependencies
 {:http [:app]
  :app  [:example]}}

And then have a system-factory function to construct the new-system function from the data structure.

(def new-system
  (system-factory (read-edn "foo/system.edn")))

As well as reducing the amount of code, it would also allow generators to more easily add components and endpoints to the system.

dadair-ca commented 8 years ago

Edit 1: Ah I found that https://github.com/duct-framework/duct/blob/master/duct/src/duct/util/namespace.clj will try to require the var. So this must be a separate issue.


Edit 2: It seems that the above only occurs for :components and :endpoints in the configuration map, so if a var is defined in say, :config, the var isn't require'd.

So in the following example, only the first var is required:

{:components {:fixtures fixtures.component/fixtures}
 :config {:fixtures {:adapter fixtures.adapters.jdbc/jdbc-adapter}}}

Edit 3: I've circumvented this issue using bindings from the namespace where load-system is called, eg:

(defn new-system []
  (let [bindings {'fixture-adapter fixtures.adapters.jdbc/jdbc-adapter}
        system (load-system (keep io/resource ["api/system.edn" "dev.edn" "local.edn"])
                            bindings)]
    system))
  :fixtures
  {:adapter fixture-adapter
   ...

I will add this to the wiki in case others come across this issue.


Is there any nuance to incorporating other libraries' components into the new edn-based configuration? Since these libraries must be require'd at some point, wouldn't their vars return ClassNotFound exceptions on use?

I'm trying to incorporate fixtures-component, but I receive errors that lead me to indicate that the vars I place in the configuration map lead to ClassNotFound Exceptions. I've created an issue over at fixtures-component with more information and stacktraces: https://github.com/banzai-inc/fixtures-component/issues/3

My overall question, is how can 3rd party components be incorporated into the configuration map? I've successfully incorporated duct components, and my own created ones, but there seems to be a nuance to using 3rd party libraries that haven't been require'd. Unless this is an error specific to fixtures-component.

weavejester commented 8 years ago

Fixed in 0.8.0.