Creative Commons Licence This work is licensed under a Creative Commons
Attribution-ShareAlike 4.0 International License

Source: gitlab

Dependency Injection Frameworks: reasons to avoid them

Andy Balaam
artificialworlds.net/blog

Contents

Dependency Injection

DI Frameworks

History lesson

How do you write code? You just write code.

public static void main(String[] args) { new WhatDayIsIt().printDay(); } public class WhatDayIsIt { void printDay() { DateTimeFormatter formatter = DateTimeFormatter .ofPattern("cccc") .withLocale(Locale.UK) .withZone(ZoneId.of("Europe/London")); System.out.println(formatter.format(Instant.now())); } }

Dependencies

What are the "dependencies" of printDay?

void printDay() { DateTimeFormatter formatter = DateTimeFormatter .ofPattern("cccc") .withLocale(Locale.UK) .withZone(ZoneId.of("Europe/London")); System.out.println(formatter.format(Instant.now())); }

Dependencies

What are the "dependencies" of printDay?

void printDay() { DateTimeFormatter formatter = DateTimeFormatter .ofPattern("cccc") .withLocale(Locale.UK) .withZone(ZoneId.of("Europe/London")); System.out.println(formatter.format(Instant.now())); }

Dependencies

What are the "dependencies" of printDay?

void printDay() { DateTimeFormatter formatter = DateTimeFormatter .ofPattern("cccc") .withLocale(Locale.UK) .withZone(ZoneId.of("Europe/London")); System.out.println(formatter.format(Instant.now())); }

Dependencies

What about formatter?

void printDay() { DateTimeFormatter formatter = DateTimeFormatter .ofPattern("cccc") .withLocale(Locale.UK) .withZone(ZoneId.of("Europe/London")); System.out.println(formatter.format(Instant.now())); }

Dependencies

What about formatter?

Dependencies

Key question:

Dependencies

// Given a PrintStream ByteArrayOutputStream os = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(os); // And a Thursday in 2019 Clock clock = Clock.fixed( ISO_INSTANT.parse("2019-09-12T16:04:00Z").query(Instant::from), ZoneId.of("Europe/London")); // When we print the day WhatDayTestable wd = new WhatDayTestable(ps, clock); wd.printDay(); // Then we printed Thursday! assert os.toString().equals("Thursday\n");

Dependency Injection

WhatDayTestable(PrintStream printStream, Clock clock) {..} void printDay() { DateTimeFormatter formatter = DateTimeFormatter .ofPattern("cccc") .withLocale(Locale.UK) .withZone(ZoneId.of("Europe/London")); printStream.println(formatter.format(Instant.now(clock))); }

Dependency Injection

What about formatter?

Dependency Injection

DI Frameworks

Manual injection:

public static void main(String[] args) { PrintStream ps = System.out; Clock clock = Clock.systemUTC(); new WhatDayTestable(ps, clock).printDay(); }

DI Frameworks

Injection via framework:

public static void main(String[] args) { Injector injector = Guice.createInjector( new AbstractModule() { @Override public void configure() { bind(PrintStream.class).toInstance(System.out); bind(Clock.class).toInstance(Clock.systemUTC()); } } ); injector.getInstance(WhatDayGuice.class).printDay(); }

DI Frameworks

DI Frameworks

Dependency Injection frameworks mean you don't have to write this:

public static void main(String[] args) { PrintStream ps = System.out; Clock clock = Clock.systemUTC(); new WhatDayTestable(ps, clock).printDay(); }

DI Frameworks

Instead you just write this:

public static void main(String[] args) { Injector injector = Guice.createInjector( new AbstractModule() { @Override public void configure() { bind(PrintStream.class).toInstance(System.out); bind(Clock.class).toInstance(Clock.systemUTC()); } } ); injector.getInstance(WhatDayGuice.class).printDay(); }

Reasons to use DI frameworks

Reasons to avoid DI frameworks

Reasons to avoid DI frameworks

1. Prevents me searching

E.g. for constructors:

$ grep 'new WhatDayGuice' ... silence ...

(Also for setters.)

Reasons to avoid DI frameworks

2. Obfuscates what is happening

bind(PrintStream.class).toInstance(System.out); bind(Clock.class).toInstance(Clock.systemUTC());

vs.

PrintStream ps = System.out; Clock clock = Clock.systemUTC();

Reasons to avoid DI frameworks

3. Is action at a distance

Reasons to avoid DI frameworks

4. It's global variables!

Reasons to avoid DI frameworks

5. Undefined behaviour

Reasons to avoid DI frameworks

6. Encourages use of singletons

Reasons to avoid DI frameworks

7. Makes singletons some magic thing

Reasons to avoid DI frameworks

8. Private injection

Reasons to avoid DI frameworks

What did you do to my unit test?

Injector injector = Guice.createInjector( new AbstractModule() { @Override public void configure() { bind(Clock.class).toInstance(clock); bind(PrintStream.class).toInstance(ps); } } ); WhatDayGuiceNoConstr wd = injector.getInstance( WhatDayGuiceNoConstr.class);

Reasons to avoid DI frameworks

8. Private injection

Reasons to avoid DI frameworks

9. Makes testing painful

Reasons to avoid DI frameworks

10. Hides the pain of huge dependency trees...

Reasons to avoid DI frameworks

10. Hides the pain of huge dependency trees...

Rant time

Reasons to use them:

Rant time

If you are too lazy to write this code:

class MyClass { MyClass(Dep1 dep1, Dep2 dep2, Dep3 dep3) { this.dep1 = dep1; this.dep2 = dep2; this.dep3 = dep3; } }

Rant time

If you are too lazy to write this code:

class MyClass { MyClass(Dep1 dep1, Dep2 dep2, Dep3 dep3) { this.dep1 = dep1; this.dep2 = dep2; this.dep3 = dep3; } }

Use Kotlin.

Rant time

Reasons to use them:

Rant time

If you are too lazy to write this code:

Dep1 dep1 = new Dep1(); Dep2 dep2 = new Dep2(dep1); Dep3 dep3 = new Dep3(dep1); MyClass mine = new MyClass(dep1, dep2, dep3);

Rant time

If you are too lazy to write this code:

Dep1 dep1 = new Dep1(); Dep2 dep2 = new Dep2(dep1); Dep3 dep3 = new Dep3(dep1); MyClass mine = new MyClass(dep1, dep2, dep3);

Dragon up.

Rant time

Dragon up.

Rant time

A programmer not thinking about dependencies is like a farmer not thinking about irrigation.
Yes, you might grow something.
Or you might not.

Rant time

Rant time

Rant time

Revisiting our example

Here is our example again:

WhatDayTestable(PrintStream printStream, Clock clock) {..} void printDay() { DateTimeFormatter formatter = DateTimeFormatter .ofPattern("cccc") .withLocale(Locale.UK) .withZone(ZoneId.of("Europe/London")); printStream.println(formatter.format(Instant.now(clock))); }

Revisiting our example

How about this instead?

String printDay(Instant time) { DateTimeFormatter formatter = DateTimeFormatter .ofPattern("cccc") .withLocale(Locale.UK) .withZone(ZoneId.of("Europe/London")); return formatter.format(time); }

Revisiting our example

static void main(String[] args) { System.out.println( new WhatDayBeing().printDay(Instant.now())); }

Revisiting our example

printDay converts Instants to day names.

Conclusions

Most of your code should be low-dependency.

Isolate highly-dependent code.


Consider making a distinction between:

More info