This talk revisits dependency injection, and attempts to answer a single question honestly, or at least while pointing out and acknowledging the biases at play: "is dependency injection a good thing?"
Dependency injection has fast established itself as a major design pattern in modern software. No longer the province of server-side and enterprise software, it is now a fundamental component of frameworks from Spring to Angular.js.
With such widespread success, the time is ripe to take a fresh look at dependency injection if we are to understand it better. After all, DI is instrumental in building large systems that are loosely coupled, and it cleanly separates your tests from implementation... or does it?
(A talk given at GeeCON 2017 in Prague, Czech Republic)
2. Full Disclosure
• I was never a fan
• I tried researching
this properly…
– Read a ton of material
– Interviewed people
– Sat and thought
• Still turned out a rant
Image: ImgFlip
3. Semantics
When I say “dependency injection”, you’re
probably thinking of this:
public class BillingModule extends AbstractModule {
@Override
protected void configure() {
bind(TransactionLog.class).to(DatabaseTransactionLog.class);
bind(CreditCardProcessor.class).to(PaypalCreditCardProcessor.class);
}
}
So did I.
4. 1. THE “D” IN SOLID
Image: Peter von Bagh, “Just Frozen Water” via Flickr (CC0 1.0 Public Domain)
5. Back to Basics
• Single responsibility
• Open/closed
• Liskov substitution principle
• Interface segregation
• Dependency inversion
Image: Michael Feathers via MozaicWorks
6. Back to Basics
• Single responsibility
• Open/closed
• Liskov substitution principle
• Interface segregation
• Dependency inversion
Image: Michael Feathers via MozaicWorks
7. Dependency Inversion
• A simple idea
• Given a dependency:
– A must not depend on B
directly
OAuthProvider
MysqlUserStore
“A”
“B”
8. Dependency Inversion
• A simple idea
• Given a dependency:
– A must not depend on B
directly
– Instead, A depends on
an abstraction of B
– B depends on the
same abstraction
OAuthProvider
MysqlUserStore
UserStore
class
class
interface
“A”
“B”
9. The Verdict
• Dependency
inversion is old hat
– Seems obvious now
– First postulated by
Uncle Bob in 1994 (!)
• We’ve come a long
way since!
“The philosophy of
one century is the
common sense
of the next.”
-- Henry Ward
Beecher
Image: Mathew Brady, “Henry Ward Beecher” via Library of Congress (Public Domain)
11. Dependency Injection
• Let’s assume SOLID…
• Given a dependency:
– Who owns it?
– What is the lifecycle?
• Traditionally:
– The depending service
manages everything
class UserService {
private UserStore store =
new MysqlUserStore(Config.JDBC_URL);
bool authenticate(String userToken) {
UserContext user =
store.lookup(userToken);
return user != null
? user.isActive()
: false;
}
}
12. Dependency Injection
• DI stipulates:
– Services should not
build dependencies
– But instead receive them
– Dependencies are state
• It does not stipulate
how to implement this
class UserService {
private UserStore store;
public UserService(UserStore store) {
this.store = store;
}
bool authenticate(String userToken) {
// ...
}
}
13. The Verdict
• Dependency
injection is good
• If taken at face value:
– No frameworks
– No containers
– No reflection
– Simply common sense
Image: Tomas Catelazo via Wikimedia Commons (CC-BY-SA 4.0)
15. Inversion of Control
• IoC is not a pattern
• It’s a design principle
• Traditionally:
– “Main” flow calls into
components
– Control flows back to
the “main” flow
Main (entry point)
• Configuration
• Bootstrapping
Event loop
• Dequeue
• Dispatch
Event handler
• Act on event
• Done
16. Inversion of Control
• IoC is not a pattern
• It’s a design principle
• With IoC:
– Control is surrendered
to a container
– Container calls into
components
Main (entry point)
• Setup
IoC container
• Bootstrapping/wiring
• Event loop
Event handler
• Act on event
• Done
17. Inversion of Control
• IoC means many things
– Servlet containers
– Plugin systems
– Stream computing
– “DI” containers
• We’ll focus on the latter
18. IoC & DI
• Consider Spring/Guice
– A runtime container
– Manages components
– … including lifecycle
– … and automatic wiring
Image: ImgFlip
19. Perceived Benefits
• Why use a container?
– Simplify wiring
– Simplify testing
– Dynamic configuration
– Support for AOP
• Let’s consider each
Image: ImgFlip
20. Perceived Benefits
• Why use a container?
– Simplify wiring
– Simplify testing
– Dynamic configuration
– Support for AOP
• Let’s consider each
Image: ImgFlip
21. Simplified Wiring
class MyApp {
DBI db = new DBIFactory().build(...);
EventStore eventStore =
new MysqlEventStore(db);
SnapshotStore snapshotStore =
new MysqlSnapshotStore(db);
Clock clock =
Clock.systemUTC();
SiteService siteService =
new DefaultSiteService(
eventStore, snapshotStore, clock);
}
22. Simplified Wiring
class MyApp {
DBI db = new DBIFactory().build(...);
EventStore eventStore =
new MysqlEventStore(db);
SnapshotStore snapshotStore =
new MysqlSnapshotStore(db);
Clock clock =
Clock.systemUTC();
SiteService siteService =
new DefaultSiteService(
eventStore, snapshotStore, clock);
}
class MyAppModule extends AbstractModule {
@Override
protected void configure() {
bind(DBI.class).toProvider(...);
bind(EventStore.class)
.to(MysqlEventStore.class);
bind(SnapshotStore.class)
.to(MysqlSnapshotStore.class);
bind(Clock.class)
.toProvider(Clock::systemUTC);
bind(SiteService.class)
.to(DefaultSiteService.class);
}
}
23. Simplified Wiring
class MyApp {
DBI db = new DBIFactory().build(...);
EventStore eventStore =
new MysqlEventStore(db);
SnapshotStore snapshotStore =
new MysqlSnapshotStore(db);
Clock clock =
Clock.systemUTC();
SiteService siteService =
new DefaultSiteService(
eventStore, snapshotStore, clock);
}
class MyAppModule extends AbstractModule {
@Override
protected void configure() {
bind(DBI.class).toProvider(...);
bind(EventStore.class)
.to(MysqlEventStore.class);
bind(SnapshotStore.class)
.to(MysqlSnapshotStore.class);
bind(Clock.class)
.toProvider(Clock::systemUTC);
bind(SiteService.class)
.to(DefaultSiteService.class);
}
}
24. Simplified Wiring
• No tangible benefit!
– Wiring is trivial
• Real, tangible downsides
– Startup time
– Code navigability
– Dynamic/reflective magic
Image: André Nordstrand, “Loss of common sense” via Flickr (CC-BY-NC 2.0)
26. Simplify Testing
deconstruction (source: dictionary.com)
Noun
1. a technique of literary analysis that regards
meaning as resulting from the differences
between words rather than their reference to
the things they stand for.
30. Simplify Testing
• This is a bad idea
– Hard to reason about
– Have to deal with
subtle interactions
– Does not reflect your
unit structure
• Most importantly…
– Leads to poor design!
Image: Viewminder, “Strange Bedfellows” via Flickr (CC-BY-NC-ND 2.0)
33. … except …
• With huge codebases
– Read: “Monoliths”
– Read: “Enterprise”
• Enables a tradeoff
– Developer discipline
– Code coherence,
simplicity, navigability
Corollary:
If you’re seeing
benefit from IoC,
your codebase is
already out of
control.
34. QUESTIONS?
Thank you for listening
tomer@tomergabel.com
@tomerg
http://engineering.wix.com
Sample Project:
http://tinyurl.com/event-sourcing-sample
This work is licensed under a Creative
Commons Attribution-ShareAlike 4.0
International License.