• Like
Devoxx 2012 (v2)
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

Devoxx 2012 (v2)

  • 617 views
Published

 

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
    Be the first to like this
No Downloads

Views

Total Views
617
On SlideShare
0
From Embeds
0
Number of Embeds
1

Actions

Shares
Downloads
4
Comments
0
Likes
0

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. Effective Dependency InjectionNicholas Behrens and Jerome Dochez Google Inc.
  • 2. This talk1. Quick DI refresher2. Guidelines for effective DI3. Composing applications using DI4. Testing code using DI5. Applying DI to existing code
  • 3. 1. Quick DI Refresher
  • 4. DI = Dependency InjectionDeclare what you need, not how to get itPaymentProcessor( CreditCardService creditCardService) { this.creditCardService = creditCardService; this.riskAssessor = new RiskAssessorImpl(...);}
  • 5. DI = Dependency Injection+ Loose coupling, facilitate reuse and testing- Complexity, runtime failuresMany frameworks with many toolsImportant to establish good conventions
  • 6. DI Framework● Providers● Bindings● @Inject● Module [= collection of Bindings]● Injector [= collection of Modules] ○ injector.getInstance(type)● Scopes
  • 7. 2. Guidelines for effective DI
  • 8. Explicit > Implicit● Ensure explicit bindings for all injected types ○ bind(RequestFilter.class);● Implicit bindings dangerous ○ Removing an explicit binding ○ Pulling in undesired explicit binding
  • 9. Inject exactly what you needBad:PaymentProcessor(User user) { this.account = user.getAccount();}Better:PaymentProcessor(Account account) { this.account = account;}
  • 10. Prefer constructor injection● private final fields● Immutability, thread-safety● Objects valid post-construction● Match developer expectations
  • 11. 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
  • 12. If you must do work in a constructor interface DataStoreProvider<T> extends CheckedProvider<T> { T get() throws DataStoreException; }Guice-specific
  • 13. If you must do work in a constructor (contd) @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
  • 14. If you must do work in a constructor (contd) @Inject ContactsFormatter( DataStoreProvider<Contacts> contactsProvider) { this.contactsProvider = contactsProvider; } String formatContacts() throws DataStoreException { Contacts contacts = contactsProvider.get(); // throws ... }Guice-specific
  • 15. If you must do work in a constructor (contd) Problems ● More complex, verbose bindings ● More setup work for tests ● Business logic creep ● ViralGuice-specific
  • 16. Avoid direct deps on Injector● No longer declaring specific deps● Injector allows dep on anything● Easy to start using Injector, hard to stop
  • 17. Avoid binding very general typesBadbindConstant().to(8080);@Inject int port;BetterbindConstant().annotatedWith(ForHttp.class) .to(8080);@Inject @ForHttp int port;
  • 18. Avoid parameterized bindingannotationsAt 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.
  • 19. Using parameterized bindingannotations safelyConsistently use constants (enums!) for parameters:@ForPort(Ports.HTTP) int httpPort;@FeatureEnabled(Features.X) boolean xEnabled;
  • 20. Keep modules fast and side-effect free Make it easy to compose modules Avoid ● bind().toInstance(...) ● requestStaticInjection(...) ● Serial eager initialization of objectsGuice-specific
  • 21. 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. SimplifyAbuseChecker.
  • 22. Use Provider to avoid unnecessaryprovisioning@Provides AbuseService provideAbuseService( @UseNewAbuseService boolean useNewAbuseService, Provider<OldAbuseServiceImpl> oldAbuseService, Provider<NewAbuseServiceImpl> newAbuseService) { return useNewAbuseService.get() ? newAbuseService.get() : oldAbuseService.get();}
  • 23. 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) { ... }}
  • 24. Requirements creep in, you addmore 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...
  • 25. 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 ...}
  • 26. and adapts the Pipeline itself.public class Pipeline() { private final PipelineVisitor[] visitors = { ... }; OutputType visit(TypeToVisit in, User user, Logginglog) { OutputType out = new OutputType(); PipelineContext context = new PipelineContext(in, user, log); for (PipelineVisitor visitor : visitors) { visitor.visit(context); } }}what about testing, isolation,...
  • 27. 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; ...}
  • 28. 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>
  • 29. Inject into Pipelinepublic class Pipeline() { private final Set<Provider<PipelineVisitor.class>>visitors @Inject Pipeline(Set<Provider<PipelineVisitor.class>> visitors) { this.visitors = visitors; } public void visit(TypeToVisit in) { for (Provider<PipelineVisitor.class> visitor :visitors) { visitor.get().visit(in); ...
  • 30. 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));}
  • 31. 3. Composing applications using DI
  • 32. Modular Java!Use a Service-Based Architecture● All services defined as an interface● Implementations package-private● Package-level Module bindsNo need for OSGi unless you need:● Runtime extensibility● Runtime versioning
  • 33. Modular Java: Declare API and ImplAPI: (interfaces, enums, binding annotations)public interface SpamChecker { void checkIsSpam(...);}Implementation: (package private)class SpamCheckerImpl implements SpamChecker { void checkIsSpam(...) { ... }}
  • 34. Modular Java: Colocate bindingsIn 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 SpamCheckerModuleand then inject SpamChecker as needed.
  • 35. Modular Java: Document depsDocument what your module requires:class SpamCheckerModule ... { @Override protected void configure() { requireBinding(Clock.class); requireBinding(CryptoService.class); ... }}
  • 36. Modular Java: Package it all up1. Maven a. All public interfaces in -api.jar module b. All implementations and modules in -impl.jar c. api module should only be used for compiling dependent modules, impl module should be used for assembling the application2. OSGi a. Export interfaces and Module only b. Avoid versioning at runtime
  • 37. 4. Testing code using DI
  • 38. Unit testing a class using DIpublic class SubjectUnderTest { @Inject SubjectUnderTest(Service1 s1, Service2 s2) { // Assign to fields } String doSomething() { ... }}
  • 39. Unit testing a class using DI (contd)Service1 mock1 = Mockito.mock(Service1.class);Service2 mock2 = Mockito.mock(Service2.class);SubjectUnderTest sut = new SubjectUnderTest( mock1, mock2);// configure mockMockito.when(mock1).service(eq("param")) .thenReturn("some return value);// test your servicesut.doSomething();// verify the results...
  • 40. Testing bindingsModule.override to overcome problematicbindings 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
  • 41. System testsSystem (end-to-end) tests a must with DIDiscover runtime failures in tests or your userswill discover them for you
  • 42. 5. Applying DI to existing code
  • 43. Migrating to DIRewiring an existing code base to use DI willtake timeDont need to use everywhere (but consistencyis good)
  • 44. Techniques for migrating to DIApproaches● Bottom-up, separate injectors for subparts● Top-down, single injector pushed downOften need to compromise on ideal patterns● Multiple Injectors● Directly reference Injector● Constructors do work
  • 45. Things that make DI difficult: deepinheritanceclass BaseService { }class MobileService extends BaseService { }class AndroidService extends MobileService { @Inject AndroidService(Foo foo, Bar bar) { super(foo, bar);}What if BaseService needs to start dependingon Baz? Prefer composition.
  • 46. Things that make DI difficult: staticmethodsclass SomeClass { static void process(Foo foo, Bar bar) { processFoo(foo); ... } static void processFoo(Foo foo) { ... }}I want to bind Foo and start injecting itelsewhere. How do I get to Guice?
  • 47. Thank you!https://developers.google.comContact us: jedo@google.com nbehrens@google.com
  • 48. Avoid @ImplementedBy @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 bindingsGuice-specific
  • 49. Constructor injection promotesthread-safe code@ThreadSafeclass Greeter { private final Greetings greetings; private final User user; @Inject Greeter(Greetings greetings, User user) { this.greetings = greetings; this.user = user; } String getGreeting(GreetingId greetingId) { ... }}
  • 50. Minimize Custom Scopes● 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, ...?
  • 51. Testing providersIf your module contains non-trivial providermethods, unit test themclass FooModuleTest { void testBarWhenBarFlagDisabled() { assertNull( new FooModule().provideBar( false /* bar flag */); } void testBarWhenBarFlagEnabled() { ... }}