To Inject or not inject, Dependency Injection in a Kotlin world (appbuilders.ch 2018)

To Inject or not inject, Dependency Injection in a Kotlin world (appbuilders.ch 2018)

Dependency Injection via frameworks like Dagger were present in most modern Android projects. But then Kotlin happened. And since then a bunch of new libraries like Koin or Kodein appeared. Developers are even writing articles how to do DI without any framework. Some argue that those don’t even offer real dependency injection. Let’s look at that argument and compare the approaches. So, is there something wrong with Dagger & co. in Kotlin? Are they obsolete? What are the alternatives? Let’s dive in.

A8b79d304b5184e5a5b0a109590f6683?s=128

Danny Preussler

April 16, 2018
Tweet

Transcript

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

    world @PreusslerBerlin flickr.com/photos/51035610542@N01/6254235315
  2. @PreusslerBerlin flickr.com/photos/uncalno/8538679708

  3. @PreusslerBerlin K.O.T.L.I.N. flickr.com/photos/xxxxx77/10886450585

  4. @PreusslerBerlin flickr.com/photos/xxxxx77/10886450585

  5. @PreusslerBerlin flickr.com/photos/xxxxx77/10886450585

  6. @PreusslerBerlin The modern history of DI 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 • It started with the new keyword: new Player(….) Nothing says “implementation” more than the new keyword 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 • Singleton Pattern • Factory Pattern • Abstract Factory Pattern • Prototype Pattern • Builder Pattern 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 • Factories everywhere 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 • 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
  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 • 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
  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 • Guice came • Spring joined with configuration via code „ JavaConfig“ flickr.com/photos/139228535@N05/40102368982
  13. @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
  14. @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
  15. @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
  16. @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
  17. @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
  18. @PreusslerBerlin Annotation processing flickr.com/photos/yuu_jp/12697848465

  19. @PreusslerBerlin • Still the way to go? • What about

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

    Dagger needs a lot of boilerplate • Back to simplicity • Limitations of Java as language @PreusslerBerlin
  21. @PreusslerBerlin How can Kotlin help? • Lazy • Delegates •

    Type inference • Inlining (generic T) • Lambdas • Functional programming @PreusslerBerlin
  22. @PreusslerBerlin Problems with Java DI and Kotlin @PreusslerBerlin

  23. @PreusslerBerlin Ugly constructors class PlayerViewModel(playlistUsecase: PlaylistUsecase) {} vs class PlayerViewModel

    @Inject constructor(playlistUsecase: PlaylistUsecase) {}
  24. @PreusslerBerlin Ugly constructors class PlayerViewModel(playlistUsecase: PlaylistUsecase) {} vs class PlayerViewModel

    @Inject constructor(playlistUsecase: PlaylistUsecase) {}
  25. @PreusslerBerlin Field changes private val viewModel: ViewModel by lazy {…

    } vs @Inject lateinit var viewModel: ViewModel
  26. @PreusslerBerlin Field changes private val viewModel: ViewModel by lazy {…

    } vs @Inject lateinit var viewModel: ViewModel
  27. @PreusslerBerlin Field changes private val viewModel: ViewModel by lazy {…

    } vs @Inject lateinit var viewModel: ViewModel
  28. @PreusslerBerlin @Inject does not see default arguments: class PlayerViewModel( val

    schedulers: RxSchedulers = DefaultSchedulers()) vs class PlayerViewModel( val schedulers: RxSchedulers) @Inject constructor(): this(DefaultSchedulers())
  29. @PreusslerBerlin @Inject does not see default arguments: class PlayerViewModel( val

    schedulers: RxSchedulers = DefaultSchedulers()) vs class PlayerViewModel( val schedulers: RxSchedulers) @Inject constructor(): this(DefaultSchedulers())
  30. @PreusslerBerlin @Inject does not see default arguments: class PlayerViewModel( val

    schedulers: RxSchedulers = DefaultSchedulers()) vs class PlayerViewModel( val schedulers: RxSchedulers) @Inject constructor(): this(DefaultSchedulers())
  31. @PreusslerBerlin & vs Java DI

  32. @PreusslerBerlin Examples (any Java DI) @Inject lateinit var binder :

    BinderFactory
  33. @PreusslerBerlin Examples (Kodein) val binder : BinderFactory by kodein.instance()

  34. @PreusslerBerlin Examples (Koin) val binder : BinderFactory by inject()

  35. @PreusslerBerlin Examples (Toothpick) bind(API::class.java).to(DummyApi::class.java) bind(BinderFactory::class.java) .toInstance(ViewDataBindingFactory())

  36. @PreusslerBerlin Examples (Toothpick Ktx) bindClass<API>(DummyApi::class) bindInstance<BinderFactory> { ViewDataBindingFactory>() }

  37. @PreusslerBerlin Examples (Kodein) bind<API>() with provider { DummyApi() } bind<BinderFactory>()

    with singleton { ViewDataBinderFactory() }
  38. @PreusslerBerlin Examples (Koin) factory{ DummyApi() as API } bean {

    ViewDataBindingFactory() as BinderFactory }
  39. @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)
  40. @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)
  41. @PreusslerBerlin Examples (Toothpick Ktx) bindProvider(HomeViewModelProvider::class) class HomeViewModelProvider @Inject constructor(val resources

    Resources) : Provider<HomeViewModel> { override fun get() = HomeViewModel(resources) }
  42. @PreusslerBerlin Examples (Toothpick Ktx) bindProvider(HomeViewModelProvider::class) class HomeViewModelProvider @Inject constructor(val resources

    Resources) : Provider<HomeViewModel> { override fun get() = HomeViewModel(resources) }
  43. @PreusslerBerlin Examples (Kodein) bind<HomeViewModel>() with provider { HomeViewModel(instance()) }

  44. @PreusslerBerlin Examples (Koin) factory { HomeViewModel(get())}

  45. @PreusslerBerlin Let‘s get academic Dependency Injection vs Service Locators

  46. @PreusslerBerlin

  47. @PreusslerBerlin Injection public MovieLister(MovieFinder finder) { this.finder = finder; }

  48. @PreusslerBerlin Injection pico.registerComponentImplementation( MovieFinder.class, ColonMovieFinder.class); pico.registerComponentImplementation( MovieLister.class);

  49. @PreusslerBerlin Injection

  50. @PreusslerBerlin Service Locator

  51. @PreusslerBerlin Service Locator public static MovieFinder movieFinder() { return soleInstance.movieFinder;

    } ... private void configure() { ServiceLocator.load( new ServiceLocator( new ColonMovieFinder("movies1.txt"))); }
  52. @PreusslerBerlin Martin says: • both provide fundamental decoupling • In

    both: application code is independent of the concrete implementation
  53. @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
  54. @PreusslerBerlin Martin says: So the decision between locator and injector

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

    It tends to be hard to understand, problems when you are trying to debug.
  56. @PreusslerBerlin &

  57. @PreusslerBerlin inline fun <reified T : Any> get(…): T =

    … resolveByClass(T::class, …) &
  58. @PreusslerBerlin • Are Service Locators NOT Dependency Injection! &

  59. @PreusslerBerlin • Are Service Locators NOT Dependency Injection! • Kodein

    is a Dependency Retrieval container … The main differences with a Service Locator are the binding scopes (singleton, multiton, provider, etc.) „ Salomon Brys, Kodein author &
  60. @PreusslerBerlin The issue is android • Normally constructor injection preferred

    • On Android: framework classes need field/setter injection: Activity, Service, Fragment, Views
  61. @PreusslerBerlin Deeper look at field/setter injection @Inject lateinit var binder

    : BinderFactory Tottally DI
  62. @PreusslerBerlin Deeper look at field/setter injection val binder by lazy

    { Locator.get<BinderFactory>() } Tottally SL
  63. @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?
  64. @PreusslerBerlin Jake admits: • 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
  65. @PreusslerBerlin So what‘s the deal? • Performance comparable • Compile-time

    validation for completeness • Code generates the boilerplate for manual DI • But: Dagger has more boilerplate than all of those? And through reified Koin reduced this
  66. @PreusslerBerlin So what‘s the deal? • Performance comparable • Compile-time

    validation for completeness • Code generates the boilerplate for manual DI • But: Dagger has more boilerplate than all of those? And through reified Koin reduced this
  67. @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/
  68. @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/
  69. @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/
  70. @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)
  71. @PreusslerBerlin Compile time validation • Dagger abandon the runtime configuration

    idea of DI • Dagger dismissed the idea of being able to deploy without config • Java is the only island that uses compile time verification* • Is this the right path?
  72. @PreusslerBerlin Compile time validation • Dagger abandon the runtime configuration

    idea of DI • Dagger dismissed the idea of being able to deploy without config • Java is the only island that uses compile time verification* • Is this the right path?
  73. @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
  74. @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
  75. @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
  76. @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 flickr.com/photos/steve-maw/4543646311
  77. @PreusslerBerlin Is this the right path? • What about the

    compile time safety for DI? • Why did the nuclear plant at Chernobyl catch fire, melt down, destroy a small city, and leave a large area uninhabitable? They overrode all the safeties. So don’t depend on safeties to prevent catastrophes flickr.com/photos/steve-maw/4543646311
  78. @PreusslerBerlin Is this the right path? • What about the

    compile time safety for DI? • „Why did the nuclear plant at Chernobyl catch fire, melt down, destroy a small city, and leave a large area uninhabitable? They overrode all the safeties. “ • So don’t depend on safeties to prevent catastrophes https://blog.cleancoder.com/uncle-bob/2017/01/11/TheDarkPath.html flickr.com/photos/steve-maw/4543646311
  79. @PreusslerBerlin What about JSR-330 and Kotlin? • A unified dependency

    injection standard for DI frameworks • javax.inject • Spring, Guice, Dagger, Toothpick all comply (partially) flickr.com/photos/abbyladybug/2097312947
  80. @PreusslerBerlin What about JSR-330 and Kotlin? • Where based on

    Java‘s lacks of modern language features • So it does not apply • But we miss a new standard for delegates like „by inject“ • Kodein does support JSR-330 (for migration purpose only) kodein-jxinject module flickr.com/photos/abbyladybug/2097312947
  81. @PreusslerBerlin Things we should learn from past • Don‘t build

    Godzilla factories • Law of Demeter • Keep configuration separate of code flickr.com/photos/-pea-/1172413560
  82. @PreusslerBerlin Sum up • „the decision between locator and injector

    depends on whether that dependency is a problem“ • We are not deploying the app without configuration on mobile • Use constructor injection where possible • Don‘t forget the rules of software engineering: Don‘t have one big service locator! • Have tests
  83. @PreusslerBerlin Transition • How easy it is depends on how

    well you hide the DI in your project flickr.com/photos/anca_bejinaru/5234037866/
  84. @PreusslerBerlin Transition • How easy it is depends on how

    well you hide the DI in your project • Painful remove all @inject constructors and replace with manual calls flickr.com/photos/anca_bejinaru/5234037866/
  85. @PreusslerBerlin Transition • How easy it is depends on how

    well you hide the DI in your project • Painful remove all @inject constructors and replace with manual calls • Might end up in awkward readable syntax: factory { HomeViewModel(get(), get(), get(), get(), get(), get()) } flickr.com/photos/anca_bejinaru/5234037866/ giphy.com
  86. @PreusslerBerlin Transition • How easy it is depends on how

    well you hide the DI in your project • Painful remove all @inject constructors and replace with manual calls • Might end up in awkward readable syntax • Kodein helps with JSR330 implementation for short term flickr.com/photos/anca_bejinaru/5234037866/
  87. @PreusslerBerlin Features you might want to have • Tags/Names •

    Scoping • Overriding / testing • Android module • Only supports interfaces? • Multi modules support! Koin ✓ Kodein ✓
  88. @PreusslerBerlin Read more • http://mvpjava.com/brief-history-dependency-injection • http://www.pacoworks.com/2018/03/11/simple-dependency- injection-in-kotlin-part-2/ • https://android.jlelse.eu/moving-from-dagger-to-koin-simplify-your-

    android-development-e8c61d80cddb • http://mvpjava.com/brief-history-dependency-injection/ • https://medium.com/@JorgeCastilloPr/kotlin-dependency-injection- with-the-reader-monad-7d52f94a482e • https://blog.gouline.net/kapsule-minimalist-dependency-injection- for-kotlin-ed3e344d60ed flickr.com/photos/139228535@N05/40102368982
  89. To inject or not inject @PreusslerBerlin