Share
Embed
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.
Danny Preussler
April 16, 2018
Transcript
89 slides
-
To Inject or not inject Dependency injection in a Kotlin
world @PreusslerBerlin flickr.com/photos/51035610542@N01/6254235315 -
@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 -
@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 -
@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 -
@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 -
@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 -
@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 -
@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 -
@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 -
@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 -
@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 -
@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 -
@PreusslerBerlin • Still the way to go? • What about
build time impact? @PreusslerBerlin -
@PreusslerBerlin The problems: • Annotations? • Too much magic •
Dagger needs a lot of boilerplate • Back to simplicity • Limitations of Java as language @PreusslerBerlin -
@PreusslerBerlin How can Kotlin help? • Lazy • Delegates •
Type inference • Inlining (generic T) • Lambdas • Functional programming @PreusslerBerlin -
@PreusslerBerlin Ugly constructors class PlayerViewModel(playlistUsecase: PlaylistUsecase) {} vs class PlayerViewModel
@Inject constructor(playlistUsecase: PlaylistUsecase) {} -
@PreusslerBerlin Ugly constructors class PlayerViewModel(playlistUsecase: PlaylistUsecase) {} vs class PlayerViewModel
@Inject constructor(playlistUsecase: PlaylistUsecase) {} -
@PreusslerBerlin Field changes private val viewModel: ViewModel by lazy {…
} vs @Inject lateinit var viewModel: ViewModel -
@PreusslerBerlin Field changes private val viewModel: ViewModel by lazy {…
} vs @Inject lateinit var viewModel: ViewModel -
@PreusslerBerlin Field changes private val viewModel: ViewModel by lazy {…
} vs @Inject lateinit var viewModel: ViewModel -
@PreusslerBerlin @Inject does not see default arguments: class PlayerViewModel( val
schedulers: RxSchedulers = DefaultSchedulers()) vs class PlayerViewModel( val schedulers: RxSchedulers) @Inject constructor(): this(DefaultSchedulers()) -
@PreusslerBerlin @Inject does not see default arguments: class PlayerViewModel( val
schedulers: RxSchedulers = DefaultSchedulers()) vs class PlayerViewModel( val schedulers: RxSchedulers) @Inject constructor(): this(DefaultSchedulers()) -
@PreusslerBerlin @Inject does not see default arguments: class PlayerViewModel( val
schedulers: RxSchedulers = DefaultSchedulers()) vs class PlayerViewModel( val schedulers: RxSchedulers) @Inject constructor(): this(DefaultSchedulers()) -
@PreusslerBerlin Examples (Kodein) bind<API>() with provider { DummyApi() } bind<BinderFactory>()
with singleton { ViewDataBinderFactory() } -
@PreusslerBerlin Examples (Koin) factory{ DummyApi() as API } bean {
ViewDataBindingFactory() as BinderFactory } -
@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) -
@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) -
@PreusslerBerlin Examples (Toothpick Ktx) bindProvider(HomeViewModelProvider::class) class HomeViewModelProvider @Inject constructor(val resources
Resources) : Provider<HomeViewModel> { override fun get() = HomeViewModel(resources) } -
@PreusslerBerlin Examples (Toothpick Ktx) bindProvider(HomeViewModelProvider::class) class HomeViewModelProvider @Inject constructor(val resources
Resources) : Provider<HomeViewModel> { override fun get() = HomeViewModel(resources) } -
@PreusslerBerlin Service Locator public static MovieFinder movieFinder() { return soleInstance.movieFinder;
} ... private void configure() { ServiceLocator.load( new ServiceLocator( new ColonMovieFinder("movies1.txt"))); } -
@PreusslerBerlin Martin says: • both provide fundamental decoupling • In
both: application code is independent of the concrete implementation -
@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 -
@PreusslerBerlin Martin says: So the decision between locator and injector
depends on whether that dependency is a problem. -
@PreusslerBerlin Martin says: DI… comes at a price too: •
It tends to be hard to understand, problems when you are trying to debug. -
@PreusslerBerlin inline fun <reified T : Any> get(…): T =
… resolveByClass(T::class, …) & -
@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 & -
@PreusslerBerlin The issue is android • Normally constructor injection preferred
• On Android: framework classes need field/setter injection: Activity, Service, Fragment, Views -
@PreusslerBerlin Deeper look at field/setter injection @Inject lateinit var binder
: BinderFactory Tottally DI -
@PreusslerBerlin Deeper look at field/setter injection val binder by lazy
{ Locator.get<BinderFactory>() } Tottally SL -
@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? -
@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 -
@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 -
@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 -
@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/ -
@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/ -
@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/ -
@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) -
@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? -
@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? -
@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 -
@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 -
@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 -
@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 -
@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 -
@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 -
@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 -
@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 -
@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 -
@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 -
@PreusslerBerlin Transition • How easy it is depends on how
well you hide the DI in your project flickr.com/photos/anca_bejinaru/5234037866/ -
@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/ -
@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 -
@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/ -
@PreusslerBerlin Features you might want to have • Tags/Names •
Scoping • Overriding / testing • Android module • Only supports interfaces? • Multi modules support! Koin ✓ Kodein ✓ -
@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