How to replace DI in Clojure?

“better” is fairly subjective. Component is simpler. It has a simple lifecycle – just start and stop – and some dependency graph logic to figure out the order to start/stop the components and what needs to be added in to each component along the way. Since the 0.4.0 release, you can extend that lifecycle protocol via metadata, which means you don’t even need to create records – see how next.jdbc implements the Component lifecycle on a plain hash map and a function to provide a simple connection pool for use with Component (next.jdbc doesn’t even need to depend on Component to implement this!).

Integrant overlays a data DSL and extra lifecycle hooks on those concepts. It has seven multimethods, representing the various lifecycle points (compared to just two in Component) and it’s about twice as much code as Component.

Where Component almost necessitates keeping the start and stop implementations close together, Integrant lets you spread the lifecycle across up to seven defmethods that can be “anywhere”. This can make it much harder to piece together what the complete lifecycle is for any given “component”.

I can appreciate Integrant’s flexibility, but I just don’t find I need that level of complexity.

Finally, most of Integrant’s rationale points apply only to the original version of Component: now that the lifecycle can be extended via metadata, you no longer need records – the lifecycle can be attached via metadata to any object that supports metadata. That was always true for Component’s dependency annotations (and you were always able to use plain hash maps, instead of records, for components that had no lifecycle functions):

  • Component supports dependencies for anything that is associative (this is still more restrictive than Integrant – but it is more than “just records”),
  • Component supports the start/stop lifecycle on anything that can carry metadata (which includes functions, although those can’t have dependencies).
10 Likes