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

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