We're planting a tree for every job application! Click here to learn more

Meet Autowire!

Sergey Tkachenko

16 Nov 2022

4 min read

Meet Autowire!
  • Clojure

In one of my previous posts I’ve shown how to setup and run a very simple TODO application. Apart from the application itself there was a bit of wiring to be done manually. This included, e.g. define and add integrant components to the config map, wrap application functions into integrant components, connect components together, etc. This is completely fine for small to medium sized projects but as your system grows, it could become a pain to do all of this yourself.

What if I’ll say you can reduce the hassle?

That’s how the Autowire module was born.

What is this?

In short, Autowire is a Duct module which will scan all your project namespaces during the application startup and will create a Duct configuration for you on the fly.

All you need to do is to add this dependency to your Clojure project

[io.github.parencat/fx "0.1.2"]

Autowire relying heavily on Clojure metadata. In Clojure you can attach any additional information to symbols, maps, vectors, functions, etc. Autowire will look for specific key pairs in the metadata and use it to figure out which namespaces to load and how to create and configure integrant components.

TODO example

Speaking of our TODO example. That’s how a Duct part would look like using Autowire (left side).

todos.png

These code samples are identical in terms of functionality. As you can see, you need only a single line in the config map to enable autowiring instead of specifying all components.

{:duct.profile/base  {...}
 ...
 :fx.module/autowire {}}

Also, you don’t need integrant multimethod wrappers. You can use metadata to hook up usual Clojure functions as integrant components. In addition, you can attach metadata to function arguments (as namespaced keywords). Autowire will treat them as dependency components, will initialize them first and inject into components where they are required.

(defn ^:fx/autowire db-connection [{:keys [db-uri]}]
  (jdbc/get-connection {:connection-uri db-uri
                        :dbtype         "sqlite"}))

(defn ^:fx/autowire close-connection {:fx/halt ::db-connection}
  [^Connection connection]
  (.close connection))

(defn ^:fx/autowire create-table [^::db-connection db]
  (jdbc/execute! db create-todo-table-query))

Despite the fact that it might look a little unusual there’s one interesting side effect. Your functions are still just Clojure functions. You don’t need to spin up the Duct system to test them or play with them in the REPL. Until you pass the correct arguments, they will continue to work. Even if you decide to get rid of Duct in the future and switch to another framework, you won't have to worry about refactoring.

How it works?

I’ve already touched briefly on the topic of namespace scanning. Autowire does two things: read classpath entries and filter out everything which is not Clojure namespace

(->> (clojure.java.classpath/classpath)
     (clojure.tools.namespace.find/find-namespaces))

Next step is actual scanning.

(let [members (ns-publics ns)
	(for [member members]
		(autowired? (meta member)))]

The autowired? function traverse through metadata map and looking for :fx/autowire key. If found, will proceed with wrapping this member in the component. Now the most interesting part. Autowire can recognize three types of components. Most common ones - functions. In this case Autowire will call defmethod on ig/init-key . Name of that key will be constructed as :[namespace]/[function-name] keyword.

;; what you writing
(defn ^:fx/autowire my-function [& args]
	...)

;; what Autowire doing
(defmethod ig/init-key component-key [_ params]
	...
	(apply component-function params-list))

Another thing to notice params wouldn’t be passed as is to the component function. While scanning members metadata, Autowire collect info from the :arglists key (standard for functions). If there’s arguments with metadata related to other components they will be included in the params-list list. Important thing - your function will be invoked during the integrant component initialization. The result of it will be included in the running system. You can add the additional metadata key :fx/wrap if you need your function for the later use. This key will tell Autowire to do all its work but return an anonymous function to include in the system.

Next type of components are functions, but with the additional metadata key :fx/halt This key should point to existing component name (namespaced keyword). These functions are used to define the behavior of the halt phase for components.

(defmethod ig/halt-key! component-key [_ init-key-result]
  (component-function init-key-result))

And finally, you can use vars as components. This type of component is useful when you need to drop something into config and use it in other components.

;; what you writing
(def ^:fx/autowire my-value
	...) ;; you can put here anything like vectors, maps, objects, etc.

;; what Autowire doing
(defmethod ig/init-key component-key [_ _]
  (deref component-value))

Another use case for vars is components with composite keys. For example

;; this could be a component provided by a third party library
(defmethod ig/init-key ::cool-component [_ config]
	;; use config to initialize component
	...)

(def ^{:fx/autowire ::cool-component} config-for-cool-comp
  (do {:something "useful"}))

Notice the usage of :fx/autowire key in metadata. Now it’s not just a boolean value, it’s a reference to an existing component. As the result, you’ll have a composite key in the final config:

{:duct.profile/base  
	{...
	 [:some-ns/cool-component :my-ns/config-for-cool-comp] {:something "useful"}
	...}

This way you can instantiate multiple instances of the same component but with different config parameters.

That’s it! Give this library a try. Hope you’ll enjoy it.

See you next time.

Did you like this article?

Sergey Tkachenko

See other articles by Sergey

Related jobs

See all

Title

The company

  • Remote

Title

The company

  • Remote

Title

The company

  • Remote

Title

The company

  • Remote

Related articles

JavaScript Functional Style Made Simple

JavaScript Functional Style Made Simple

Daniel Boros

12 Sep 2021

JavaScript Functional Style Made Simple

JavaScript Functional Style Made Simple

Daniel Boros

12 Sep 2021

WorksHub

CareersCompaniesSitemapFunctional WorksBlockchain WorksJavaScript WorksAI WorksGolang WorksJava WorksPython WorksRemote Works
hello@works-hub.com

Ground Floor, Verse Building, 18 Brunswick Place, London, N1 6DZ

108 E 16th Street, New York, NY 10003

Subscribe to our newsletter

Join over 111,000 others and get access to exclusive content, job opportunities and more!

© 2024 WorksHub

Privacy PolicyDeveloped by WorksHub