Share
Embed
Transcript
103 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 is full of it • 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 Constructors class PlayerViewModel(playlistUsecase: PlaylistUsecase) {} vs class PlayerViewModel @Inject
constructor(playlistUsecase: PlaylistUsecase) {} -
@PreusslerBerlin Constructors class PlayerViewModel(playlistUsecase: PlaylistUsecase) {} vs class PlayerViewModel @Inject
constructor(playlistUsecase: PlaylistUsecase) {} -
@PreusslerBerlin Default arguments class PlayerViewModel( val schedulers: RxSchedulers = DefaultSchedulers())
vs class PlayerViewModel( val schedulers: RxSchedulers) @Inject constructor(): this(DefaultSchedulers()) -
@PreusslerBerlin Default arguments class PlayerViewModel( val schedulers: RxSchedulers = DefaultSchedulers())
vs class PlayerViewModel( val schedulers: RxSchedulers) @Inject constructor(): this(DefaultSchedulers()) -
@PreusslerBerlin Default arguments class PlayerViewModel( val schedulers: RxSchedulers = DefaultSchedulers())
vs class PlayerViewModel( val schedulers: RxSchedulers) @Inject constructor(): this(DefaultSchedulers()) -
@PreusslerBerlin Fields private val viewModel: ViewModel by lazy {… }
vs @Inject lateinit var viewModel: ViewModel -
@PreusslerBerlin Fields private val viewModel: ViewModel by lazy {… }
vs @Inject lateinit var viewModel: ViewModel -
@PreusslerBerlin Fields private val viewModel: ViewModel by lazy {… }
vs @Inject lateinit var viewModel: ViewModel -
@PreusslerBerlin Bindings (Kodein) bind<API>() with provider { DummyApi() } bind<BinderFactory>()
with singleton { ViewDataBinderFactory() } -
@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 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) -
@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 void configure() { ServiceLocator.load( new ServiceLocator( new
ColonMovieFinder("movies1.txt"))); } ... public static MovieFinder movieFinder() { return soleInstance.movieFinder; } -
@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 The issue is android • Normally constructor injection preferred
• On Android: framework classes need field/setter injection: Activity, Service, Fragment, Views Android central -
@PreusslerBerlin Deeper look at field/setter injection @Inject lateinit var binder
: BinderFactory Totally 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 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 -
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/ -
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/ -
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/ -
@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 -
@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 -
@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 -
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/ -
@PreusslerBerlin inline fun <reified T : Any> get(…): T =
… resolveByClass(T::class, …) -
@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 -
@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 } -
@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 without auto wiring, DI container become pointless Manual constructor registration makes it harder to refactor (Mark Seemann) -
@PreusslerBerlin Where are we? • Choice depends on scale •
Hard dependency to SL framework • Performance comparable • Compile-time validation -
@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 -
@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. “ -
@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 -
@PreusslerBerlin Override for tests (Koin) val resources = mock<Resources>() val
homeViewModel = HomeViewModel() @BeforeEach fun setup() { StandAloneContext.loadKoinModules( applicationContext { bean { resources } bean { homeViewModel } }) } -
@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 -
@PreusslerBerlin Acess the real thing in test (Koin) dependencies {
testImplementation 'org.koin:koin-test:0.9.3‘ } val preferences: Persistence by inject() -
@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/ -
@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/ -
@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/ -
@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/ -
@PreusslerBerlin Transition • Might end up in awkward readable syntax
factory { HomeViewModel(get(), get(), get(), get(), get(), get()) } flickr.com/photos/anca_bejinaru/5234037866/ -
@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") -
@PreusslerBerlin Scoping • Toothpick Ktx: scope(SCOPE, { module { //
bindings • Koin: applicationContext { context(SCOPE) { // bindings }} • Kodein Kodein { bind<API>() with scoped(SCOPE)… -
@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 -
@PreusslerBerlin Other Features • Android module • Koin has direct
support for architecture components • Multi modules support • Non JVM Kotlin projects Koin ✓ Kodein ✓ -
@PreusslerBerlin Sum up • Koin: • Pro: lightweight, easy •
Contra: Limited • Kodein • Pro: Feature rich • Contra: Too many features • DI is simple concept, keep it simple! -
@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 -
@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