Dependency Injection with KODEIN

Dependency Injection with KODEIN

Presentation about Dependency Injection fundamentals and how to achieve DI in Kotlin-powered applications using the KODEIN framework.

Presentation given at

- Kotlin Meetup SP #02 (January/2018)

D4b7a3e2ed10f86e0b52498713ba2601?s=128

Ubiratan Soares

January 11, 2018
Tweet

Transcript

  1. 2.
  2. 3.
  3. 5.

    class Controller(private val source: DataSource) { fun doSomeWork() { val

    service = Service(ds) val formatter = OutputFormatter() render(formatter.format(service.getResults())) } }
  4. 6.

    class Controller(private val source: DataSource) { fun doSomeWork() { val

    service = Service(ds) val formatter = OutputFormatter() render(formatter.format(service.getResults())) } }
  5. 7.

    class Controller( private val service: Service, private val formatter: OutputFormatter)

    { fun doSomeWork() { val results = service.getResults() render(formatter.format(results)) } } class Service(private val source: DataSource) { } class DataSource( private val logger: Logger, private val tracker: ErrorTracker, // other dependencies ) class OutputFormatter { }
  6. 8.

    class Controller( private val service: Service, private val formatter: OutputFormatter)

    { fun doSomeWork() { val results = service.getResults() render(formatter.format(results)) } } class Service(private val source: DataSource) { } class DataSource( private val logger: Logger, private val tracker: ErrorTracker, // other dependencies ) class OutputFormatter { }
  7. 9.

    Enforcing DI means that any class with an available public

    constructor gets all its collaborators at instantiation time
  8. 17.

    • Manual code setup of all objects creation and DAG

    relationships • Extra code needed for scoped- based dependencies • Reflection-free • No DI errors at runtime • Boilterplate • Semi automated code setup, driven by JSR330 annotations standard • Reflection-based (Guice, etc) or reflection-free (Dagger2) • Reflection-based solutions may crash at runtime due to dependency resolution errors • Less code than static factories Static Factories JSR330
  9. 19.

    The Dependency Inversion Principle (from SOLID) "High level policies should

    not depend directly on low-level details; instead, low-level details should depend on high level policies. To accomplish this, runtime dependencies can be decoupled from source code dependencies via a shared polymorphic abstraction"
  10. 20.

    interface Logger { fun log(severity: Level, toLog : String) }

    class SL4JLogger : Logger { override fun log(severity: Level, toLog: String) { // Print to console } } class LogEntriesLogger : Logger { override fun log(severity: Level, toLog: String) { // Send to external logging service } }
  11. 23.
  12. 24.

    OVERVIEW • 100% Kotlin dependency injection framework • Heavily based

    on Kotlin features : type system and lazy properties • Reflection-based • Mirrors some API style from Guice, but has its own style as well • Supports Kotlin-JVM, Android, JS and Kotlin-native
  13. 25.

    object Injector { val graph = Kodein.lazy { bind<Gson>() with

    singleton { Gson() } bind<OkHttpClient>() with singleton { OkHttpClient.Builder().build() } bind<RestCaller>() with provider { RestCaller(httpClient = instance()) } // More bindings ...
  14. 26.

    object Injector { val graph = Kodein.lazy { bind<Gson>() with

    singleton { Gson() } bind<OkHttpClient>() with singleton { OkHttpClient.Builder().build() } bind<RestCaller>() with provider { RestCaller(httpClient = instance()) } // More bindings ... The inferred LazyKodein subtype represents the object graph
  15. 27.

    object Injector { val graph = Kodein.lazy { bind<Gson>() with

    singleton { Gson() } bind<OkHttpClient>() with singleton { OkHttpClient.Builder().build() } bind<RestCaller>() with provider { RestCaller(httpClient = instance()) } // More bindings ... Instances will be created in a lazy way
  16. 28.

    object Injector { val graph = Kodein.lazy { bind<Gson>() with

    singleton { Gson() } bind<OkHttpClient>() with singleton { OkHttpClient.Builder().build() } bind<RestCaller>() with provider { RestCaller(httpClient = instance()) } // More bindings ... Defines a binding, eg, a way to provide an object instance
  17. 29.

    object Injector { val graph = Kodein.lazy { bind<Gson>() with

    singleton { Gson() } bind<OkHttpClient>() with singleton { OkHttpClient.Builder().build() } bind<RestCaller>() with provider { RestCaller(httpClient = instance()) } // More bindings ... Defines the lifecycle time - or scope - of this object
  18. 30.

    object Injector { val graph = Kodein.lazy { bind<Gson>() with

    singleton { Gson() } bind<OkHttpClient>() with singleton { OkHttpClient.Builder().build() } bind<RestCaller>() with provider { RestCaller(httpClient = instance()) } // More bindings ... Pulls a dependency transitively from the graph, with a proper scope
  19. 31.

    bind<APIGateway>() with provider { APIGateway( homeService = instance(), brokerService =

    instance(), walletService = instance() ) } You should use named parameters to clarify the dependencies that are being pulled transitively
  20. 32.

    bind<Scheduler>(UITHREAD) with singleton { AndroidSchedulers.mainThread() } bind<Scheduler>(WORKER) with singleton {

    Schedulers.io() } bind<SomeBussinessRule>() with provider { SomeInfrastructure( storage = instance(), webService = instance(), worker = instance(WORKER) ) } companion object { val WORKER = "worker" val UITHREAD = "main" }
  21. 33.

    You can bind multiple instances of same type using tags

    bind<Scheduler>(UITHREAD) with singleton { AndroidSchedulers.mainThread() } bind<Scheduler>(WORKER) with singleton { Schedulers.io() } bind<SomeBussinessRule>() with provider { SomeInfrastructure( storage = instance(), webService = instance(), worker = instance(WORKER) ) } companion object { val WORKER = "worker" val UITHREAD = "main" }
  22. 35.

    bind<OkHttpClient>() with singleton { OkHttpClient.Builder().build() } bind<RestCaller>() with provider {

    RestCaller(httpClient = instance()) } This dependency will live as long the Kodein instance lives
  23. 36.

    bind<OkHttpClient>() with singleton { OkHttpClient.Builder().build() } bind<RestCaller>() with provider {

    RestCaller(httpClient = instance()) } Each time this dependency will be required, a new instance will be created
  24. 37.

    You may have a dependency bounded with a custom scope,

    eg, this dependency will live as long the provided object lives bind<Presenter>() with scopedSingleton(androidActivityScope) { Presenter( scheduler = instance(UITHREAD), view = it ) } The object itself is accessible at binding time
  25. 39.

    fun main(args: Array<String>) { val gateway: APIGateway by Injector.graph.instance() gateway.start()

    } Our class holds the LazyKodein property No class reference needed to retrieve an instance from the graph
  26. 40.

    class App : Application(), KodeinAware { override val kodein by

    Injection(context = this).graph } class SomeActivity : AppCompatActivity(), SomeView { private val kodein by lazy { LazyKodein(appKodein) } private val presenter by kodein.with(this).instance<Presenter>() // Do your work … }
  27. 41.

    class App : Application(), KodeinAware { override val kodein by

    Injection(context = this).graph } class SomeActivity : AppCompatActivity(), SomeView { private val kodein by lazy { LazyKodein(appKodein) } private val presenter by kodein.with(this).instance<Presenter>() // Do your work … } This interface defines an instance that can hold a dependency graph
  28. 42.

    class App : Application(), KodeinAware { override val kodein by

    Injection(context = this).graph } class SomeActivity : AppCompatActivity(), SomeView { private val kodein by lazy { LazyKodein(appKodein) } private val presenter by kodein.with(this).instance<Presenter>() // Do your work … } Provided by the Android support
  29. 43.

    class App : Application(), KodeinAware { override val kodein by

    Injection(context = this).graph } class SomeActivity : AppCompatActivity(), SomeView { private val kodein by lazy { LazyKodein(appKodein) } private val presenter by kodein.with(this).instance<Presenter>() // Do your work … } Provides the custom dependency scope
  30. 44.
  31. 45.

    MUCH MORE • More ways to provide and retrieve dependencies

    • More ways to define dependencies scopes • Modules support, including dependency overriding • Multibindings support • JSR330 inter-operability for injections • Great documentation
  32. 47.

    CONCLUSIONS • Kodein is a great Kotlin first-class DI framework

    • You dont have to follow any JSR330 conventions to define the object graph • Kodein has same pain-points that reflection-based DI framework have : not so fast when compared with reflection-free solutions, errors may occur at runtime due to dependencies resolution failures • Great alternative for DI on Android applications
  33. 49.

    UBIRATAN SOARES Computer Scientist by ICMC/USP Software Engineer, curious guy

    Google Developer Expert for Android Teacher, speaker, etc, etc