I’m currently working in a an environment where most developers are Object-Oriented fanatics. Given that we develop in Java, I think that it is a good thing - save the fanatics part. In particular, I’ve run across a deeply-entrenched meme that states that modeling Rich Domain Objects and using Spring dependency injection at the same time is not possible. Not only is this completely false, it reveals a lack of knowledge of Spring features, one I’ll be trying to correct in this article.
However, my main point is not about Spring but about whatever paradigm one holds most dear, be it Object-Oriented Programming, Functional Programming, Aspect-Oriented Programming or whatever. Those are only meant to give desired properties to software: unit-testable, readable, whatever… So one should always focus on those properties than on the way of getting them. Remember:
When the wise man points at the moon, the idiot looks at the finger.
Back to the problem at hand. The basic idea is to have some bean having reference on some service to do some stuff (notice the real-word notions behind this idea…) so you could call:
someBean.doStuff()
The following is exactly what not to do:
public class TopCaller {
@Autowired
private StuffService stuffService;
public SomeBean newSomeBean() {
return new SomeBeanBuilder().with(stuffService).build();
}
}
This design should be anathema to developers promoting unit-testing as the new()
deeply couples the TopCaller
and SomeBeanBuilder
classes.
There’s no way to stub this dependency in a test, it prevents testing the createSomeBean()
method in isolation.
What you need is to inject SomeBean
prototypes into the SomeBeanBuilder
singleton.
This is method injection and is possible within Spring with the help of lookup methods (I’ve already blogged about that some time ago and you should probably have a look at it).
public abstract class TopCaller {
@Autowired
private StuffService stuffService;
public SomeBean newSomeBean() {
return newSomeBeanBuilder().with(stuffService).build();
}
public abstract SomeBeanBuilder newSomeBeanBuilder();
}
With the right configuration, Spring will take care of providing a different SomeBeanBuilder
each time the newSomeBeanBuilder()
method is called.
With this new design, we changed the strong coupling between TopCaller
and SomeBeanBuilder
to a soft coupling, one that can be stubbed in tests and allow for unit-testing.
In the current design, it seems the only reason for SomeBeanBuilder
to exist is to pass the StuffService
from the TopCaller
to SomeBean
instances.
There is no need to keep it with method injection.
There are two different possible improvements:
- Given our "newfound" knowledge, inject:
- method
newSomeBean()
instead ofnewSomeBeanBuilder()
intoTopCaller
StuffService
directly intoSomeBean
- method
- Keep
StuffService
as aTopCaller
attribute and pass it every timedoStuff()
is invoked
I would favor the second option since I frown upon a Domain Object keeping references to singleton services, unless there are many parameters to pass at every call. As always, there’s not a single best choice, but only contextual choices.
Also, I would also use explicit dependency management instead of some automagic stuff, but that another debate.
I hope this piece proved beyond any doubt that Spring does not prevent Rich Domain Model, far from it. As a general rule, know about tools you use and go their way instead of following some path just for the sake of it .