+ All Categories
Home > Documents > Devoxx 2012 (v2)

Devoxx 2012 (v2)

Date post: 05-Dec-2014
Category:
Upload: jerome-dochez
View: 883 times
Download: 3 times
Share this document with a friend
Description:
 
53
Effective Dependency Injection Nicholas Behrens and Jerome Dochez Google Inc.
Transcript
Page 1: Devoxx 2012 (v2)

Effective Dependency Injection

Nicholas Behrens and Jerome DochezGoogle Inc.

Page 2: Devoxx 2012 (v2)

This talk

1. Quick DI refresher

2. Guidelines for effective DI

3. Composing applications using DI

4. Testing code using DI

5. Applying DI to existing code

Page 3: Devoxx 2012 (v2)

1. Quick DI Refresher

Page 4: Devoxx 2012 (v2)

Declare what you need, not how to get it

PaymentProcessor(

CreditCardService creditCardService) {

this.creditCardService = creditCardService;

this.riskAssessor =

new RiskAssessorImpl(...);

}

DI = Dependency Injection

Page 5: Devoxx 2012 (v2)

DI = Dependency Injection

+ Loose coupling, facilitate reuse and testing- Complexity, runtime failures

Many frameworks with many tools

Important to establish good conventions

Page 6: Devoxx 2012 (v2)

DI Framework

● Providers● Bindings● @Inject● Module [= collection of Bindings]● Injector [= collection of Modules]

○ injector.getInstance(type)

● Scopes

Page 7: Devoxx 2012 (v2)

2. Guidelines for effective DI

Page 8: Devoxx 2012 (v2)

Explicit > Implicit

● Ensure explicit bindings for all injected types○ bind(RequestFilter.class);

● Implicit bindings dangerous○ Removing an explicit binding○ Pulling in undesired explicit binding

Page 9: Devoxx 2012 (v2)

Inject exactly what you need

Bad:PaymentProcessor(User user) {

this.account = user.getAccount();

}

Better:PaymentProcessor(Account account) {

this.account = account;

}

Page 10: Devoxx 2012 (v2)

Prefer constructor injection

● private final fields

● Immutability, thread-safety

● Objects valid post-construction

● Match developer expectations

Page 11: Devoxx 2012 (v2)

Avoid work in constructors

● Separation of concerns, facilitates testing,

"standard" constructor

● In Guice: runtime ProvisionException

easily propagates up to top level

● Initialize after constructing object graph

Page 12: Devoxx 2012 (v2)

If you must do work in a constructor

interface DataStoreProvider<T> extends CheckedProvider<T> {

T get() throws DataStoreException;

}

Guice-specific

Page 13: Devoxx 2012 (v2)

If you must do work in a constructor (cont'd)@CheckedProvides(DataStoreProvider.class)

Contacts provideContacts(UserId userId, DataStore dataStore)

throws DataStoreException {

return dataStore.readUserContacts(userId);

}

@Override protected void configure() {

install(ThrowingProviderBinder.forModule(this));

}

Guice-specific

Page 14: Devoxx 2012 (v2)

@Inject ContactsFormatter(

DataStoreProvider<Contacts> contactsProvider) {

this.contactsProvider = contactsProvider;

}

String formatContacts() throws DataStoreException {

Contacts contacts = contactsProvider.get(); // throws

...

}

If you must do work in a constructor (cont'd)

Guice-specific

Page 15: Devoxx 2012 (v2)

Problems

● More complex, verbose bindings

● More setup work for tests

● Business logic creep

● Viral

If you must do work in a constructor (cont'd)

Guice-specific

Page 16: Devoxx 2012 (v2)

● No longer declaring specific deps

● Injector allows dep on anything

● Easy to start using Injector, hard to stop

Avoid direct deps on Injector

Page 17: Devoxx 2012 (v2)

Avoid binding very general types

BadbindConstant().to(8080);

@Inject int port;

BetterbindConstant().annotatedWith(ForHttp.class)

.to(8080);

@Inject @ForHttp int port;

Page 18: Devoxx 2012 (v2)

At first it seems convenient...@Named("http") int httpPort;

...but then you do@Named("HTTP") int httpPort;

No help from the IDE, more runtime errors.Harder to track down injection sites.

Avoid parameterized binding annotations

Page 19: Devoxx 2012 (v2)

Consistently use constants (enums!) for parameters:

@ForPort(Ports.HTTP) int httpPort;

@FeatureEnabled(Features.X) boolean xEnabled;

Using parameterized binding annotations safely

Page 20: Devoxx 2012 (v2)

Make it easy to compose modules

Avoid● bind().toInstance(...)● requestStaticInjection(...)● Serial eager initialization of objects

Keep modules fast and side-effect free

Guice-specific

Page 21: Devoxx 2012 (v2)

Prefer Null Objects over @Nullable@Provides AbuseService provideAbuseService(

@ForAbuse String abuseServiceAddress) {

if (abuseServiceAddress.isEmpty()) { return null; }

return new AbuseServiceImpl(abuseServiceAddress);

}

class AbuseChecker {

@Inject AbuseChecker(

@Nullable AbuseService abuseService) { ... }

void doAbuseCheck(...) {

if (abuseService != null) { ... )

}

Bind StubAbuseService instead of null. Simplify AbuseChecker.

Page 22: Devoxx 2012 (v2)

Use Provider to avoid unnecessary provisioning@Provides AbuseService provideAbuseService(

@UseNewAbuseService boolean useNewAbuseService,

Provider<OldAbuseServiceImpl> oldAbuseService,

Provider<NewAbuseServiceImpl> newAbuseService) {

return useNewAbuseService.get()

? newAbuseService.get()

: oldAbuseService.get();

}

Page 23: Devoxx 2012 (v2)

Scoping as an alternative to contextpublic interface PipelineVisitor() {

void visit(TypeToVisit typeToVisit);

}

public class Pipeline() {

private final PipelineVisitor[] visitors = {

new Visitor1(), new Visitor2(), ...

}

public void visit(TypeToVisit typeToVisit) {

...

}

}

Page 24: Devoxx 2012 (v2)

Requirements creep in, you add more parameters...public interface PipelineVisitor() {

void visit(TypeToVisit typeToVisit, User user);}public interface PipelineVisitor() {

void visit(TypeToVisit typeToVisit, User user, Logger logger);

}public interface PipelineVisitor() {

void visit(TypeToVisit typeToVisit, User user, Logger logger, RequestParams params);

}

and so on...

Page 25: Devoxx 2012 (v2)

Ah yes, the Context to the rescuepublic interface PipelineVisitor() {

void visit(PipelineContext context);}public Class PipelineContext {

TypeToVisit typeToVisit;User user;Logging logging;... and many more ...

}

Page 26: Devoxx 2012 (v2)

and adapts the Pipeline itself.public class Pipeline() {

private final PipelineVisitor[] visitors = { ... };OutputType visit(TypeToVisit in, User user, Logging

log){

OutputType out = new OutputType();PipelineContext context =

new PipelineContext(in, user, log);for (PipelineVisitor visitor : visitors) {

visitor.visit(context);}

}}

what about testing, isolation,...

Page 27: Devoxx 2012 (v2)

First : remove the contextpublic interface Visitor {

void visit(TypeToVisit in);}

public class LoggerVisitor {private final Logger logger;@Inject public LoggerVisitor(Logger logger) {...}void visit(InputType in, Output out) {

logger.log(Level.INFO, "Visiting " + in);}public class UserDependentVisitor {

private final User user;...

}

Page 28: Devoxx 2012 (v2)

Second : use a Multi-Binderclass VisitorsModule extend AbstractModule {

@Override protected void configure() {Multibinder<PipelineVisitor> visitorBinder =

Multibinder.newSetBinder(binder(), PipelineVisitor.class);

visitorBinder.addBinding().to(LoggerVisitor.class);visitorBinder.addBinding()

.to(UserDependentVisitor.class);}

Now we can inject a Set<Visitor>

Page 29: Devoxx 2012 (v2)

Inject into Pipelinepublic class Pipeline() {

private final Set<Provider<PipelineVisitor.class>> visitors

@InjectPipeline(Set<Provider<PipelineVisitor.class>> visitors) {

this.visitors = visitors;}

public void visit(TypeToVisit in) {for (Provider<PipelineVisitor.class> visitor :

visitors){

visitor.get().visit(in);...

Page 30: Devoxx 2012 (v2)

Testing Visitors and Pipelinepublic void testVisitor() {

Visitor v = new FirstVisitor(...);v.visit(in);assert(...)

}

public void testPipeline() {Visitor v = Mockito.mock(Visitor.class);Pipeline p = new Pipeline(Sets.of(Providers.of(v)));TypeToVisit in = new TypeToVisit(...);p.visit(in);Mockito.verify(v).visit(eq(in),

any(Output.class));}

Page 31: Devoxx 2012 (v2)

3. Composing applications using DI

Page 32: Devoxx 2012 (v2)

Modular Java!

Use a Service-Based Architecture ● All services defined as an interface● Implementations package-private● Package-level Module binds No need for OSGi unless you need: ● Runtime extensibility● Runtime versioning

Page 33: Devoxx 2012 (v2)

API: (interfaces, enums, binding annotations)

public interface SpamChecker {

void checkIsSpam(...);

}

Implementation: (package private)

class SpamCheckerImpl implements SpamChecker {

void checkIsSpam(...) { ... }

}

Modular Java: Declare API and Impl

Page 34: Devoxx 2012 (v2)

In the same package as SpamChecker*:

public class SpamCheckerModule extends AbstractModule {

@Override

public void configure() {

bind(SpamCheckerImpl.class).in(Singleton.class);

bind(SpamChecker.class).to(SpamCheckerImpl.class);

}

}

To use in your app, install SpamCheckerModule and then inject SpamChecker as needed.

Modular Java: Colocate bindings

Page 35: Devoxx 2012 (v2)

Modular Java: Document deps

Document what your module requires:

class SpamCheckerModule ... {

@Override protected void configure() {

requireBinding(Clock.class);

requireBinding(CryptoService.class);

...

}

}

Page 36: Devoxx 2012 (v2)

Modular Java: Package it all up

1. Mavena. All public interfaces in -api.jar moduleb. All implementations and modules in -impl.jarc. api module should only be used for compiling

dependent modules, impl module should be used for assembling the application

2. OSGia. Export interfaces and Module onlyb. Avoid versioning at runtime

Page 37: Devoxx 2012 (v2)

4. Testing code using DI

Page 38: Devoxx 2012 (v2)

Unit testing a class using DI

public class SubjectUnderTest {

@Inject

SubjectUnderTest(Service1 s1, Service2 s2) {

// Assign to fields

}

String doSomething() { ... }

}

Page 39: Devoxx 2012 (v2)

Unit testing a class using DI (cont'd)

Service1 mock1 = Mockito.mock(Service1.class);

Service2 mock2 = Mockito.mock(Service2.class);

SubjectUnderTest sut = new SubjectUnderTest(

mock1, mock2);

// configure mock

Mockito.when(mock1).service(eq("param"))

.thenReturn("some return value);

// test your service

sut.doSomething();

// verify the results...

Page 40: Devoxx 2012 (v2)

Module.override to overcome problematic bindings for testing:

Module testModule = Modules

.override(new AcmeModule())

.with(new AbstractModule() {

@Override protected void configure() {

bind(AuthChecker.class)

.toInstance(new StubAuthChecker());

}

});

Avoid overriding too many bindings

Testing bindings

Page 41: Devoxx 2012 (v2)

System (end-to-end) tests a must with DI

Discover runtime failures in tests or your users will discover them for you

System tests

Page 42: Devoxx 2012 (v2)

5. Applying DI to existing code

Page 43: Devoxx 2012 (v2)

Rewiring an existing code base to use DI will take time

Don't need to use everywhere (but consistency is good)

Migrating to DI

Page 44: Devoxx 2012 (v2)

Approaches● Bottom-up, separate injectors for subparts● Top-down, single injector pushed down

Often need to compromise on ideal patterns● Multiple Injectors● Directly reference Injector● Constructors do work

Techniques for migrating to DI

Page 45: Devoxx 2012 (v2)

Things that make DI difficult: deep inheritanceclass BaseService { }

class MobileService extends BaseService { }

class AndroidService extends MobileService {

@Inject AndroidService(Foo foo, Bar bar) {

super(foo, bar);

}

What if BaseService needs to start depending on Baz? Prefer composition.

Page 46: Devoxx 2012 (v2)

Things that make DI difficult: static methodsclass SomeClass {

static void process(Foo foo, Bar bar) {

processFoo(foo); ...

}

static void processFoo(Foo foo) { ... }

}

I want to bind Foo and start injecting it elsewhere. How do I get to Guice?

Page 47: Devoxx 2012 (v2)

Thank you!

https://developers.google.com

Contact us:[email protected]

[email protected]

Page 48: Devoxx 2012 (v2)
Page 49: Devoxx 2012 (v2)
Page 50: Devoxx 2012 (v2)

@ImplementedBy(MySqlUserStore.class)

interface UserStore { ... }

● Guice-ism, provided for convenience● Interface should not have compile-time dep

on specific implementation!● Can be overridden by explicit bindings

Avoid @ImplementedBy

Guice-specific

Page 51: Devoxx 2012 (v2)

@ThreadSafeclass Greeter { private final Greetings greetings;

private final User user;

@InjectGreeter(Greetings greetings, User user) {

this.greetings = greetings;this.user = user;

}

String getGreeting(GreetingId greetingId) { ... }

}

Constructor injection promotes thread-safe code

Page 52: Devoxx 2012 (v2)

● Adds complexity

● Scopes mismatches

○ e.g. request scoped type into singleton scoped type

● More (wrong) choices for developers

○ Should I put this in scopeA, scopeB, scopeC, ...?

Minimize Custom Scopes

Page 53: Devoxx 2012 (v2)

Testing providers

If your module contains non-trivial provider methods, unit test them

class FooModuleTest {

void testBarWhenBarFlagDisabled() {

assertNull(

new FooModule().provideBar(

false /* bar flag */);

}

void testBarWhenBarFlagEnabled() { ... }

}


Recommended