Not using dependency injection - how do I share services around?

480 views
Skip to first unread message

Colin Yates

unread,
May 10, 2013, 7:04:20 AM5/10/13
to clo...@googlegroups.com
(newbie, getting better each day!)

I assume we all know DI.  Through the use of a central registry I can register a service (a bean in a Spring bean factory for example).  I also define consumers of that service in the same registry passing in the configured *instance* of that service.

In Clojure I have a service (i.e. a datasource) defined in its own namespace.  What is idiomatic Clojure?:

 1) to use (defonce *data-source*...) so that every body who requires that ns gets the same instance?
 2) to provide a 'get-ds' accessor which returns a new instance and rely on passing that service along to every function that needs it?
 3) some other way I don't know about

Option 1 seems to be less-typing, but now functions aren't pure - they depend upon state defined elsewhere.  I can change the binding through 'with-XYZ' type functions, but that isn't solving the non-explicit dependency between the function and the state.

Option 2 means functions are still pure, but how do you prevent huge lists of services - i.e. if func-a calls func-b which calls func-c and func-c needs service-a then func-a and func-b need to access service-a.  Yuck.  It also means the main entry point to my application needs to assemble all of these services up in one go.

To be more explicit - DI containers provide a graphs of logic coupled with state - the state being the instances of the collaborators (i.e. "I will have ConsumerA with an instance of SimpleServiceA please").  Clojure has very strong opinions about how to manage state.  

How does the Clojure community handle this use case of separating out the definition of a service, the configuration of that service and providing that service as a collaborator to a consumer?

Thanks a bunch.

Col

Chris Ford

unread,
May 10, 2013, 7:24:42 AM5/10/13
to Clojure
A good question. One way is to use partial application to bake the data source into a fn:

(def read (partial read-from-db *data-source*))


--
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Ben Mabey

unread,
May 10, 2013, 9:14:29 AM5/10/13
to clo...@googlegroups.com
Hi Colin,

On 5/10/13 5:04 AM, Colin Yates wrote:
>
> 1) to use (defonce *data-source*...) so that every body who requires
> that ns gets the same instance?
While this has been done I view this as an antipattern. The big problem
with this approach is that you now can only have a single *data-source*
for your entire application. Testing becomes hard, stopping and
restarting is difficult, etc... While this approach is easy the
resulting workarounds introduce incidental complexity.
> 2) to provide a 'get-ds' accessor which returns a new instance and
> rely on passing that service along to every function that needs it?

Propagating the dependency like this is as much as an antipattern in
Clojure as it is in OO languages[1] IMO. As you point out it is also
just as much fun. :)
> 3) some other way I don't know about

The way I've been doing DI like stuff is with Prismatic's graph[2] and
similar libraries. While graph doesn't explicitly say it is a DI
library it certainly is solving the same problem. What is interesting
about graph is how small it is and how it can solve problems in the
large (like an application) but also on a much smaller scale (e.g.
computations with a handful of functions). While graph's approach may
not be as powerful as the huge DI frameworks in Java (I'm assuming, I
haven't used them personally much) I think you can get 90% of the value
with 1% of the code. I've been using a graph-like library for a number
of months in my more complex applications and it has been fantastic.

HTH,
Ben

1. http://picocontainer.codehaus.org/propagating-dependency-antipattern.html
2.
http://blog.getprismatic.com/blog/2012/10/1/prismatics-graph-at-strange-loop.html
https://github.com/Prismatic/plumbing

Colin Yates

unread,
May 10, 2013, 9:22:47 AM5/10/13
to clo...@googlegroups.com
Thanks both - some good suggestions.  After years of Java I am loving how 'symmetrical' everything is in Clojure (I guess in Lisp).   Thanks for the library references.


--
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to

For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- You received this message because you are subscribed to a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/keid7IGzKjk/unsubscribe?hl=en.
To unsubscribe from this group and all its topics, send an email to clojure+unsubscribe@googlegroups.com.

Timo Mihaljov

unread,
May 10, 2013, 3:14:16 PM5/10/13
to clo...@googlegroups.com
On 10.05.2013 14:04, Colin Yates wrote:
> 2) to provide a 'get-ds' accessor which returns a new instance and rely
> on passing that service along to every function that needs it?

For what it's worth, some people in the OO community, most notably Nat
Pryce and Steve Freeman of Growing Object-Oriented Software[1] fame,
advocate[2][3] this approach over using an IoC container.

> Option 2 means functions are still pure, but how do you prevent huge
> lists of services - i.e. if func-a calls func-b which calls func-c and
> func-c needs service-a then func-a and func-b need to access service-a.
> Yuck. It also means the main entry point to my application needs to
> assemble all of these services up in one go.

Here's the punchline from [3]:

If I later find that I can�t get access to some component that I
think I need, that�s not necessarily a bad thing. It�s telling me
that I�m introducing a new dependency and sometimes that�s a hint
that a component is in the wrong place, or that I�m trying to use
it from the wrong place. The coding bump is a design feedback
mechanism that I miss when I can just pull objects out of a
container. If I do a good job, I should find that, most of the
time, I have just the right components at the time that I need
them.

[1] http://www.growing-object-oriented-software.com/
[2] http://www.natpryce.com/articles/000783.html
[3]
http://www.higherorderlogic.com/2011/07/is-dependency-injection-like-facebook/

--
Timo

Colin Yates

unread,
May 10, 2013, 4:21:06 PM5/10/13
to clo...@googlegroups.com
Thanks Timo; Interesting links.  Loving Clojure, but boy is it challenging the stuff I have been doing for the past how-ever many years :).

On 10 May 2013 20:14, Timo Mihaljov <ti...@mihaljov.info> wrote:
On 10.05.2013 14:04, Colin Yates wrote:
>  2) to provide a 'get-ds' accessor which returns a new instance and rely
> on passing that service along to every function that needs it?

For what it's worth, some people in the OO community, most notably Nat
Pryce and Steve Freeman of Growing Object-Oriented Software[1] fame,
advocate[2][3] this approach over using an IoC container.

> Option 2 means functions are still pure, but how do you prevent huge
> lists of services - i.e. if func-a calls func-b which calls func-c and
> func-c needs service-a then func-a and func-b need to access service-a.
>  Yuck.  It also means the main entry point to my application needs to
> assemble all of these services up in one go.

Here's the punchline from [3]:

    If I later find that I can’t get access to some component that I
    think I need, that’s not necessarily a bad thing. It’s telling me
    that I’m introducing a new dependency and sometimes that’s a hint
    that a component is in the wrong place, or that I’m trying to use

    it from the wrong place. The coding bump is a design feedback
    mechanism that I miss when I can just pull objects out of a
    container. If I do a good job, I should find that, most of the
    time, I have just the right components at the time that I need
    them.

[1] http://www.growing-object-oriented-software.com/
[2] http://www.natpryce.com/articles/000783.html
[3]
http://www.higherorderlogic.com/2011/07/is-dependency-injection-like-facebook/

--
Timo
--
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to

For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/keid7IGzKjk/unsubscribe?hl=en.
To unsubscribe from this group and all its topics, send an email to clojure+u...@googlegroups.com.

Korny Sietsma

unread,
May 10, 2013, 8:47:59 PM5/10/13
to clo...@googlegroups.com
It's interesting to note that clojure.java.jdbc used to use our first option - they had a dynamically bound var *db* that you assigned using the (with-connection) macro.

As of version 0.0.3 this has been deprecated in favour of something like your second option - now you pass an explicit db parameter to each function, where 'db' can be a db connection specification, or an explicit connection already created from a with-transaction macro, or a number of other alternatives.  It's worth having a look through the source:

- Kornt


--
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.

For more options, visit https://groups.google.com/groups/opt_out.
 
 



--
Kornelis Sietsma  korny at my surname dot com http://korny.info
.fnord { display: none !important; }

Sean Corfield

unread,
May 11, 2013, 3:21:14 AM5/11/13
to clo...@googlegroups.com
Korny mentioned java.jdbc and I figured that was a good in to talk
about how we use it at World Singles. Even with the old API we used a
function in a specific namespace that returned the data source (in
fact it returned a pooled data source, using c3p0). Behind the scenes,
we actually use an atom to provide a cached, singleton instance.
with-redefs allows us to mock that for testing, if needed :)

I haven't missed DI at all since moving to Clojure - after decades of
OO - and I still use it in the non-Clojure, OO code that could be
considered our "legacy" system that wraps our Clojure code.

Clojure makes me think about my dependencies and organize them in a
very clean top-to-bottom tree, with very clear divisions between
subsystems. In the OO world, DI makes you sloppy... You can have
circular dependencies. You can easily add whatever dependencies you
need. You don't have to think about it, you can work around problems
that crop up.

Does that help Colin?

Sean
> --
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clo...@googlegroups.com
> Note that posts from new members are moderated - please be patient with your
> first post.
> To unsubscribe from this group, send email to
> clojure+u...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en
> ---
> You received this message because you are subscribed to the Google Groups
> "Clojure" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to clojure+u...@googlegroups.com.
> For more options, visit https://groups.google.com/groups/opt_out.
>
>



--
Sean A Corfield -- (904) 302-SEAN
An Architect's View -- http://corfield.org/
World Singles, LLC. -- http://worldsingles.com/

"Perfection is the enemy of the good."
-- Gustave Flaubert, French realist novelist (1821-1880)

Chris Ford

unread,
May 11, 2013, 4:44:16 AM5/11/13
to Clojure
Sounds to me like there's enough meat in this topic for someone to consider submitting a talk to the upcoming EuroClojure or Clojure/conj on what Clojure means for DI. It's a commonly asked question, and it could be an opportunity for The Clojure Way (TM) to influence practices across language boundaries.

Colin Yates

unread,
May 11, 2013, 4:48:02 AM5/11/13
to clo...@googlegroups.com

Yes it does, thanks.  It is amazing how much you can do in the typical spring/hibernate stack with a decent IDE without engaging your brain :). 

Clojure involves far less ceremony and really does expose you to the raw elements of your problem domain and make you think.

This is of course a good thing, but boy is it quite humbling :).  No more procrastinating by setting up JPA and thinking long and hard about "Java, Annotations or good old XML?".

I am definitely at the stage where I think Clojure's simplicity is very hard (according to Rich's "simple made easy" talk).  Not implying Clojure's simplicity is only the lack of ceremonial frameworks!

Loving it, and yes, looking back I can see how easy it is to lose your solution amongst the staggering amount of incidental complexity.

I guess my (rambling) point is to reiterate that it is very easy to plaster over symptoms/effects using the very powerful framework beasts.  The lack of them forces you to think, and hopefully remove the cause.

Finally,  I have worked with some fantastic developers who happen to use Java to build incredibly elegant and transparent solutions.  I have just worked with far more code monkeys, myself being one of them :).

You received this message because you are subscribed to a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/keid7IGzKjk/unsubscribe?hl=en.
To unsubscribe from this group and all its topics, send an email to clojure+u...@googlegroups.com.

Jimmy

unread,
May 11, 2013, 5:37:29 AM5/11/13
to clo...@googlegroups.com
Do any of the clojure books cover this topic?
Message has been deleted

abp

unread,
May 11, 2013, 6:40:04 AM5/11/13
to clo...@googlegroups.com
Well, you could also watch Stuart Sierras talks on structuring functional programs:

Clojure in the Large

http://vimeo.com/46163090

Thinking in Data & Functional Design Patterns

Colin Yates

unread,
May 11, 2013, 4:07:56 PM5/11/13
to clo...@googlegroups.com

Not specifically, nope.

On 11 May 2013 10:37, "Jimmy" <jimmy...@gmail.com> wrote:
Do any of the clojure books cover this topic?

Jason Wolfe

unread,
May 11, 2013, 8:27:11 PM5/11/13
to clo...@googlegroups.com
Hi Colin,

This is one of the reasons we created graph:


which is a general declarative mechanism for describing complex function compositions.  There's not an awesome public example yet, but we use Graph at Prismatic to build our production services, where each node builds a single component of a service, based on other named other components and parameters.  This ends up looking somewhat similar to dependency injection, although the details are rather different.  Basically you get the advantages of your second option (no global state), but hopefully without the 'yuck'.

If you're interested, I'm happy to answer questions here or on the plumbing mailing list:


Cheers,
Jason

Colin Yates

unread,
May 15, 2013, 8:31:10 AM5/15/13
to clo...@googlegroups.com
Thanks Jason, I will take a look.

Colin Yates

unread,
May 15, 2013, 9:15:17 AM5/15/13
to clo...@googlegroups.com
Nice videos - thanks for the heads up.
Reply all
Reply to author
Forward
0 new messages