Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Danny Preussler - To Inject Or Not To Inject

Danny Preussler - To Inject Or Not To Inject

Avatar for droidcon Berlin

droidcon Berlin

July 17, 2018
Tweet

More Decks by droidcon Berlin

Other Decks in Programming

Transcript

  1. To Inject or not inject Dependency injection in a Kotlin

    world @PreusslerBerlin flickr.com/photos/51035610542@N01/6254235315
  2. @PreusslerBerlin 1994 Design Patterns Late 90s Age of Factories Early

    2000 J2EE, Java 1.3 Service Locators 2003 Spring 2007 Guice 2010 JSR 330 2011 RoboGuice 2013 Dagger 2015 Dagger 2 2017 Dagger Android • It started with the new keyword: new Player(….) Nothing says “implementation” more than the new keyword flickr.com/photos/139228535@N05/40102368982
  3. @PreusslerBerlin 1994 Design Patterns Late 90s Age of Factories Early

    2000 J2EE, Java 1.3 Service Locators 2003 Spring 2007 Guice 2010 JSR 330 2011 RoboGuice 2013 Dagger 2015 Dagger 2 2017 Dagger Android • Singleton Pattern • Factory Pattern • Abstract Factory Pattern • Prototype Pattern • Builder Pattern flickr.com/photos/139228535@N05/40102368982
  4. @PreusslerBerlin 1994 Design Patterns Late 90s Age of Factories Early

    2000 J2EE, Java 1.3 Service Locators 2003 Spring 2007 Guice 2010 JSR 330 2011 RoboGuice 2013 Dagger 2015 Dagger 2 2017 Dagger Android • Factories everywhere flickr.com/photos/139228535@N05/40102368982
  5. @PreusslerBerlin 1994 Design Patterns Late 90s Age of Factories Early

    2000 J2EE, Java 1.3 Service Locators 2003 Spring 2007 Guice 2010 JSR 330 2011 RoboGuice 2013 Dagger 2015 Dagger 2 2017 Dagger Android • Context, Locator, Registers • Java1.3 had Serviceloader buildin in jar spec • The Godzilla Factory Context.getInstance().getCollaboratorY().getCollaboratorZ(); • Hive, Avalon, Tapestry IOC, Pico. (often based on Interfaces) flickr.com/photos/139228535@N05/40102368982
  6. @PreusslerBerlin 1994 Design Patterns Late 90s Age of Factories Early

    2000 J2EE, Java 1.3 Service Locators 2003 Spring 2007 Guice 2010 JSR 330 2011 RoboGuice 2013 Dagger 2015 Dagger 2 2017 Dagger Android • The Dawn of Dependency Injection • Idea: register the objects and let them handle the creation • Started with xml configuration • With JDK 1.5 came annotation support @AutoWired flickr.com/photos/139228535@N05/40102368982
  7. @PreusslerBerlin 1994 Design Patterns Late 90s Age of Factories Early

    2000 J2EE, Java 1.3 Service Locators 2003 Spring 2007 Guice 2010 JSR 330 2011 RoboGuice 2013 Dagger 2015 Dagger 2 2017 Dagger Android • Guice came • Spring joined with configuration via code „ JavaConfig“ flickr.com/photos/139228535@N05/40102368982
  8. @PreusslerBerlin 1994 Design Patterns Late 90s Age of Factories Early

    2000 J2EE, Java 1.3 Service Locators 2003 Spring 2007 Guice 2010 JSR 330 2011 RoboGuice 2013 Dagger 2015 Dagger 2 2017 Dagger Android • 2010 annotations standardized: javax.inject flickr.com/photos/139228535@N05/40102368982
  9. @PreusslerBerlin 1994 Design Patterns Late 90s Age of Factories Early

    2000 J2EE, Java 1.3 Service Locators 2003 Spring 2007 Guice 2010 JSR 330 2011 RoboGuice 2013 Dagger 2015 Dagger 2 2017 Dagger Android • RoboGuice started by community • Reflection based Google: don‘t use it flickr.com/photos/139228535@N05/40102368982
  10. @PreusslerBerlin 1994 Design Patterns Late 90s Age of Factories Early

    2000 J2EE, Java 1.3 Service Locators 2003 Spring 2007 Guice 2010 JSR 330 2011 RoboGuice 2013 Dagger 2015 Dagger 2 2017 Dagger Android • Dagger1 by Square • „similar“ to Guice • Pro: moved from Reflection to compile time • Contra: Graph composition still at runtime, Ugly genereated code flickr.com/photos/139228535@N05/40102368982
  11. @PreusslerBerlin 1994 Design Patterns Late 90s Age of Factories Early

    2000 J2EE, Java 1.3 Service Locators 2003 Spring 2007 Guice 2010 JSR 330 2011 RoboGuice 2013 Dagger 2015 Dagger 2 2017 Dagger Android • Dagger2 by Google • Fixed the drawbacks • Complete compile time verified flickr.com/photos/139228535@N05/40102368982
  12. @PreusslerBerlin 1994 Design Patterns Late 90s Age of Factories Early

    2000 J2EE, Java 1.3 Service Locators 2003 Spring 2007 Guice 2010 JSR 330 2011 RoboGuice 2013 Dagger 2015 Dagger 2 2017 Dagger Android • Android extensions for Dagger flickr.com/photos/139228535@N05/40102368982
  13. @PreusslerBerlin • Still the way to go? • What about

    build time impact? @PreusslerBerlin
  14. @PreusslerBerlin The problems: • Annotations? • Too much magic •

    Dagger is full of it • Back to simplicity • Limitations of Java as language @PreusslerBerlin
  15. @PreusslerBerlin How can Kotlin help? • Lazy • Delegates •

    Type inference • Inlining (generic T) • Lambdas • Functional programming @PreusslerBerlin
  16. @PreusslerBerlin Default arguments class PlayerViewModel( val schedulers: RxSchedulers = DefaultSchedulers())

    vs class PlayerViewModel( val schedulers: RxSchedulers) @Inject constructor(): this(DefaultSchedulers())
  17. @PreusslerBerlin Default arguments class PlayerViewModel( val schedulers: RxSchedulers = DefaultSchedulers())

    vs class PlayerViewModel( val schedulers: RxSchedulers) @Inject constructor(): this(DefaultSchedulers())
  18. @PreusslerBerlin Default arguments class PlayerViewModel( val schedulers: RxSchedulers = DefaultSchedulers())

    vs class PlayerViewModel( val schedulers: RxSchedulers) @Inject constructor(): this(DefaultSchedulers())
  19. @PreusslerBerlin The annotated contructor issue • @Inject to mark constructor

    is handy: class HomeViewModel @Inject constructor(val resources: Resources) • Violates separations of concerns • Violates clean architecture : DI should stay in outermost layer • Providers are the cleaner solution anyway (part of JSR330)
  20. @PreusslerBerlin The annotated constructor issue • @Inject to mark constructor

    is handy: class HomeViewModel @Inject constructor(val resources: Resources) • Violates separations of concerns • Violates clean architecture : DI should stay in outermost layer • Providers are the cleaner solution anyway (part of JSR330)
  21. @PreusslerBerlin The annotated contructor issue • @Inject to mark constructor

    is handy: class HomeViewModel @Inject constructor(val resources: Resources) • Violates separations of concerns • Violates clean architecture : DI should stay in outermost layer • Providers are the cleaner solution anyway (part of JSR330)
  22. @PreusslerBerlin The annotated contructor issue • @Inject to mark constructor

    is handy: class HomeViewModel @Inject constructor(val resources: Resources) • Violates separations of concerns • Violates clean architecture : DI should stay in outermost layer • Providers are the cleaner solution anyway (part of JSR330)
  23. @PreusslerBerlin Service Locator void configure() { ServiceLocator.load( new ServiceLocator( new

    ColonMovieFinder("movies1.txt"))); } ... public static MovieFinder movieFinder() { return soleInstance.movieFinder; }
  24. @PreusslerBerlin Martin says: • both provide fundamental decoupling • In

    both: application code is independent of the concrete implementation
  25. @PreusslerBerlin Martin says: important difference is about how implementation is

    provided • With service locator: the class asks for it • With injection there is no explicit request • With a Service Locator every user has a dependency to the locator
  26. @PreusslerBerlin Martin says: So the decision between locator and injector

    depends on whether that dependency is a problem.
  27. @PreusslerBerlin Martin says: DI… comes at a price too: •

    It tends to be hard to understand, problems when you are trying to debug.
  28. @PreusslerBerlin The issue is android • Normally constructor injection preferred

    • On Android: framework classes need field/setter injection: Activity, Service, Fragment, Views Android central
  29. @PreusslerBerlin Deeper look at field/setter injection val binder: BinderFactory by

    inject() It depends? We can decide when looking behind the delegate But isn’t that the same as Daggers generated code?
  30. @PreusslerBerlin Jake Wharton: • Ultimately Dagger is: • a service

    locator that forces requests be public API • moves validation to compile-time • and lookup is done by code generation • For Service Locators: • Dependencies are no longer part of the API contract. https://twitter.com/JakeWharton/status/908423738244427776
  31. Mark Seeman says: • Just because an API looks like

    that it doesn't automatically means that it's a Service Locator. • It becomes a Service Locator if used incorrectly: when application code (as opposed to infrastructure code) actively queries a service … then it has become a Service Locator. • Service Locator is ultimately not identified by the mechanics of its API, but by the role it plays. http://blog.ploeh.dk/2011/08/25/ServiceLocatorrolesvs.mechanics/ http://blog.ploeh.dk/2010/09/29/TheRegisterResolveReleasepattern/
  32. Mark Seeman says: • Just because an API looks like

    that it doesn't automatically means that it's a Service Locator. • It becomes a Service Locator if used incorrectly: when application code (as opposed to infrastructure code) actively queries a service … then it has become a Service Locator. • Service Locator is ultimately not identified by the mechanics of its API, but by the role it plays. http://blog.ploeh.dk/2011/08/25/ServiceLocatorrolesvs.mechanics/ http://blog.ploeh.dk/2010/09/29/TheRegisterResolveReleasepattern/
  33. Mark Seeman says: • Just because an API looks like

    that it doesn't automatically means that it's a Service Locator. • It becomes a Service Locator if used incorrectly: when application code (as opposed to infrastructure code) actively queries a service … then it has become a Service Locator. • Service Locator is ultimately not identified by the mechanics of its API, but by the role it plays. http://blog.ploeh.dk/2011/08/25/ServiceLocatorrolesvs.mechanics/ http://blog.ploeh.dk/2010/09/29/TheRegisterResolveReleasepattern/
  34. @PreusslerBerlin Jake Wharton: • Ultimately Dagger is: • a service

    locator that forces requests be public API • moves validation to compile-time • and lookup is done by code generation • For Service Locators: • Dependencies are no longer part of the API contract. https://twitter.com/JakeWharton/status/908423738244427776
  35. @PreusslerBerlin Jake Wharton: • Ultimately Dagger is: • a service

    locator that forces requests be public API • moves validation to compile-time • and lookup is done by code generation • For Service Locators: • Dependencies are no longer part of the API contract. https://twitter.com/JakeWharton/status/908423738244427776
  36. @PreusslerBerlin Jake Wharton: • Ultimately Dagger is: • a service

    locator that forces requests be public API • For Service Locators: • Dependencies are no longer part of the API contract. https://twitter.com/JakeWharton/status/908423738244427776
  37. Mark Seeman says: • generic <T> create() • is not

    an API that any client should consume • violates SOLID and encapsulation. http://blog.ploeh.dk/2014/05/15/service-locator-violates-solid/ http://blog.ploeh.dk/2015/10/26/service-locator-violates-encapsulation/
  38. @PreusslerBerlin • „Kodein is a Dependency Retrieval container • injection

    is more pure … It is however more cumbersome, and does not provide a lot of features. • retrieval is easier and feature full, but it requires the class to know about Kodein. • In the end, it boils down to that question: Do you need this class to be Kodein independant? http://kodein.org/Kodein-DI/?5.0/getting-started#_injection_vs_retrieval
  39. @PreusslerBerlin Suggestion to hide framework (Kodein) class MyActivity : Activity()

    { class Deps( val ds: DataSource, val ctrl: controller) val deps by lazy { (applicationContext as MyApplication) .creator.myActivity() } val ds by lazy { deps.ds } val ctrl by lazy { deps.ctrl }
  40. @PreusslerBerlin Boilerplate • Manual dependency injection has zero fixed cost

    …scales poorly. • A service locator has some overhead in creating it, inserting the bindings, and then doing the lookups. • A dependency injector has a lot of fixed overhead in setting up an injector hierarchy (components in Dagger 2) and all the modules to provide bindings. https://www.reddit.com/r/androiddev/comments/8ch4cg/dagger2_vs_koin_for_dependency_injection/
  41. @PreusslerBerlin Boilerplate • if you're going to write a serious

    app with hundreds of bindings and hundreds of injected types with a deep graph of types then you're better off with a proper injector that generates the code that you otherwise manually write worth Koin. https://www.reddit.com/r/androiddev/comments/8ch4cg/dagger2_vs_koin_for_dependency_injection/ only true when using @ constructor without auto wiring, DI container become pointless Manual constructor registration makes it harder to refactor (Mark Seemann)
  42. @PreusslerBerlin Where are we? • Choice depends on scale •

    Hard dependency to SL framework • Performance comparable • Compile-time validation
  43. @PreusslerBerlin Is this the right path? • Whose job is

    it to manage the nulls. The language? Or the programmer? • Defects are the fault of programmers. It is programmers who create defects – not languages. • And what is it that programmers are supposed to do to prevent defects? • TEST! flickr.com/photos/steve-maw/4543646311
  44. @PreusslerBerlin Is this the right path? • Whose job is

    it to manage the nulls. The language? Or the programmer? • Defects are the fault of programmers. It is programmers who create defects – not languages. • And what is it that programmers are supposed to do to prevent defects? • TEST! https://blog.cleancoder.com/uncle-bob/2017/01/11/TheDarkPath.html
  45. @PreusslerBerlin Is this the right path? „Why did the nuclear

    plant at Chernobyl catch fire, melt down, destroy a small city, and leave a large area uninhabitable? https://blog.cleancoder.com/uncle-bob/2017/01/11/TheDarkPath.html So don’t depend on safeties to prevent catastrophes They overrode all the safeties. “
  46. @PreusslerBerlin Tests • Koin has dryRun()to verify the module setup

    • Have a test spawning up all fragments/activities to see if breaking change was added
  47. @PreusslerBerlin Override for tests (Koin) val resources = mock<Resources>() val

    homeViewModel = HomeViewModel() @BeforeEach fun setup() { StandAloneContext.loadKoinModules( applicationContext { bean { resources } bean { homeViewModel } }) }
  48. @PreusslerBerlin Hide via custom Extension/Rule (Koin) val resources = mock<Resources>()

    val homeViewModel = HomeViewModel() @JvmField @RegisterExtension val extension = KoinExtension(resources.bind(),homeViewModel.bind()) https://proandroiddev.com/testing-with-koin-ade8a46eb4d
  49. @PreusslerBerlin Acess the real thing in test (Koin) dependencies {

    testImplementation 'org.koin:koin-test:0.9.3‘ } val preferences: Persistence by inject()
  50. @PreusslerBerlin Transition • Depends on how well you hide the

    DI in your project • Replace all @inject with manual binding calls will be painful flickr.com/photos/anca_bejinaru/5234037866/
  51. @PreusslerBerlin Transition • Depends on how well you hide the

    DI in your project • Replace all @inject with manual binding calls will be painful (could be a first step) flickr.com/photos/anca_bejinaru/5234037866/
  52. @PreusslerBerlin Transition • Depends on how well you hide the

    DI in your project • Replace all @inject with manual binding calls will be painful • Kodein helps with reflection based javax.inject implementation flickr.com/photos/anca_bejinaru/5234037866/
  53. @PreusslerBerlin Transition • Depends on how well you hide the

    DI in your project • Replace all @inject with manual binding calls will be painful • Kodein helps with reflection based javax.inject implementation (keeps your project running) flickr.com/photos/anca_bejinaru/5234037866/
  54. @PreusslerBerlin Transition • Might end up in awkward readable syntax

    factory { HomeViewModel(get(), get(), get(), get(), get(), get()) } flickr.com/photos/anca_bejinaru/5234037866/
  55. @PreusslerBerlin Tags/Names • Dagger/Toothpick: @Named(„api1“) • Koin: bean <Api>((" api1

    ") { …. } by inject („api“) • Kodein bind<Api>(tag = “api1") with … by kodein.instance(tag = “api1")
  56. @PreusslerBerlin Scoping • Toothpick Ktx: scope(SCOPE, { module { //

    bindings • Koin: applicationContext { context(SCOPE) { // bindings }} • Kodein Kodein { bind<API>() with scoped(SCOPE)…
  57. @PreusslerBerlin Scoping Problems with Koin • Scope opening happens automatically

    • You can not have same class in multiple scope definition • Passing arguments still possible via map in inject() • General issue with lazy: can not be reset! Be careful with leaks
  58. @PreusslerBerlin Other Features • Android module • Koin has direct

    support for architecture components • Multi modules support • Non JVM Kotlin projects Koin ✓ Kodein ✓
  59. @PreusslerBerlin Sum up • Koin: • Pro: lightweight, easy •

    Contra: Limited • Kodein • Pro: Feature rich • Contra: Too many features • DI is simple concept, keep it simple!
  60. @PreusslerBerlin Give it a try • Kodein https://github.com/Kodein-Framework/Kodein-DI • Koin

    https://github.com/Ekito/koin • Toothpick KTX https://github.com/sporttotal-tv/toothpick-kotlin-extensions
  61. @PreusslerBerlin Read more • http://mvpjava.com/brief-history-dependency-injection • https://www.martinfowler.com/articles/injection.html • http://blog.ploeh.dk/2011/08/25/ServiceLocatorrolesvs.mechanics •

    https://android.jlelse.eu/moving-from-dagger-to-koin-simplify-your- android-development-e8c61d80cddb flickr.com/photos/139228535@N05/40102368982