Paul Hammant's Blog: It turns out we didn't need Dependency Injection containers after all
I’ve been using two frameworks recently: Jooby for server-side web application construction (in conjunction with Client-side MVC frameworks) and Cuppa instead of JUnit or TestNG for my test runner. They’ve confirmed something I’ve suspected for a while: that the lack of a main() method for 1998’s J2EE technologies was the root cause of an entire side industry in Java. Perhaps that was also a multi-billion dollar design mistake.
DI container golden hammer
I (among others) was wrong to shove DI containers down people’s throats in 2002, and Inversion of Control containers from 2000, onwards. For me those containers were PicoContainer (co-created with Cucumber’s Aslak Hellosøy) and Apache’s now-deleted Avalon. Dependency Injection c ontainers were a solution to a proximate cause. The root cause problem being that J2EE apps didn’t have primordial entry points where you could compose everything. That was true for Enterprise Java Beans (EJB) and the Java Servlet spec. The latter was something you could work with, as long as you used something from the early days of Java open source to make it less bad. The proximate cause of the problem, back then, was a woolly: the available component lookup mechanisms were problematic architecturally, hindered testability, and made it difficult to compose whole solutions. If we had focused on the latter and run that root-cause analysis process to conclusion we would have found it - that a main() method would have solved all (I’m racing ahead). The relative inexperience of people in their low thirties, I guess, and going ahead and publishing containers/frameworks. Well, we had no alternate choice back then, as J2EE and Sun’s ways were not open. In 2004 we had met Scott Crawford who was interviewing the community for a modification to EJB and he’d taken back the need for setter injection but not constructor injection, so things moved far more slowly than the community really needed.
See also:
- The principles of containment
- What brought me to Inversion of Control in the first place
- An ontology: component vs class vs object vs service vs application vs process vs library, etc.
Antidotes
In a main()
method you get to compose a solution entirely. You’re in a stage before the framework has taken over. Before
your solution is listening on a socket and accepting requests. A stage when
everything is still a library or a component (or a remote service). You get to instantiate things in an order that makes
sense. You can still be guided by what constructors suggest is needed for each instantiation. Yes, Constructor Injection
as a practice is without a doubt the best language-level mechanism for dependency declaration (but without a container).
That’s true for dynamic languages as well as static languages.
Without that, we had to employ a bunch of dirty tricks to get decomposed logic to work with servlets, filters and listeners.
Tricks like thread-local. If not using containers/frameworks others in the industry used
GoF singletons (not to be confused with scope/idiom @Singleton
in Spring and Guice) and the
Service Locator pattern. Both of those are manifestations
of the problematic “shared static state”. I’m back to negatives again though, so onwards to modern examples of how it should be.
Jooby
Much more recently Jooby allowed highly decomposed Java web solutions to be created. A developer using Jooby owns enough of the boot cycle to feel there is no glass ceiling to this way of working. There could be of course, but I have not discovered it yet. Sure, Jooby uses Java 8’s lambdas extensively, but there is nothing to suggest that a more verbose Java 1.4 way of building Jooby would not have been possible back in 2002. The facilitator was an embeddable web container. Jetty had that back then, and Tomcat would delivery the same capability soon after. least.
From Jooby’s home page:
import org.jooby.Jooby;
public class App extends Jooby {
{
get("/", () -> "Hey Jooby!");
}
public static void main(final String[] args) {
run(App::new, args);
}
}
Google’s own internal “Google Servlet Environment”
(GSE) was rumored to be a fork of Tomcat, but I couldn’t see evidence for that when I poked
through the source one day in 2007. GSE apps did all launch from the main method though, and call start()
to listen on
a socket when the app had been composed. In 2005/6, Googler Bob Lee (with help from Jesse Wilson and Dhanji Prasanna)
made Guice in order to participate better in the main()
method way of
making web solutions. Better than SpringFramework at least (back then, before SpringBoot).
Cuppa
So I thought I’d look at Cuppa as I want the same way of working in my test logic. I’m sick of doing inheritance trickery to make DI work in the unwieldy JUnit and TestNG.
Cuppa is a BDD v1 test runner for Java with describe('something')
and it('should do something')
throughout the
source. It also uses Java 8’s
lambdas allowing for the a compositional nature (7/10; Java, could do better. See me after class). BDD v2 is the
Given/When/Then stuff - both v1 and v2 are from the mind of Dan North. Aslak’s
Cucumber is more popular than Dan’s JBehave these days (I’m a committer
on JBehave as it happens).
I’m not doing any inheritance trickery any more. Or annotations, or naming conventions. Without a container, I’m nesting things arbitrarily like so (an outline, not actual code):
Unit tests
# test 1, 2, 3 etc
Service tests using HTTP
for BAR mode of operation
with direct usage of dependent services
# test 4, 5, 6 etc
while recording direct usage of dependent services
# test 4, 5, 6 etc (reuse of instance above)
using mountebank instead of dependent services (playback)
# test 4, 5, 6 etc (reuse of instance above)
for FOO mode of operation
with direct usage of dependent services
# test 4, 5, 6 etc (reuse of instance above)
while recording direct usage of dependent services
# test 4, 5, 6 etc (reuse of instance above)
using mountebank instead of dependent services (playback)
# test 4, 5, 6 etc
UI component tests
# todo
Full stack functional tests
# alsoTodo
If I’d been trying for such a sophisticated suite in TestNG I’d have been attempting to use the Dependency Injection capability that it supports. I’ve used that before to some success I’ve not tried out JUnit5 and its new DI capability since it shipped, so can’t say how much catch up they did to TestNG in that respect or what the development experience is like.
From Cuppa’s home page:
@Test
public class ListTest {
{
describe("List", () -> {
describe("#indexOf", () -> {
it("returns -1 when the value is not present", () -> {
List<Integer> list = Arrays.asList(1, 2, 3);
assertThat(list.indexOf(5)).isEqualTo(-1);
});
});
});
}
}
Of course, Cuppa still needs taking forwards. The Intellij-plugin doesn’t yet allow for “run/debug a node (and below)”. But I’m still pleased I refactored a JUnit4 codebase to Cuppa in a single commit for one of my side projects.
I’d also previously tried Oleaster and can’t remember why I stopped the experiment. There’s also Spek for the JVM language Kotlin which is very elegant and does have a “run/debug a node (and below)” plugin for Intellij. I don’t know of any others that are Java8 based test runners, and if there are could people let me know?