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. Dependency Injection with KODEIN Ubiratan Soares January / 2017

  2. DI 101

  3. None
  4. Dependency Injection is all about a separation of concerns between

    some object creation and usage
  5. class Controller(private val source: DataSource) { fun doSomeWork() { val

    service = Service(ds) val formatter = OutputFormatter() render(formatter.format(service.getResults())) } }
  6. class Controller(private val source: DataSource) { fun doSomeWork() { val

    service = Service(ds) val formatter = OutputFormatter() render(formatter.format(service.getResults())) } }
  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 { }
  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 { }
  9. Enforcing DI means that any class with an available public

    constructor gets all its collaborators at instantiation time
  10. "NO MERCY !!! NO PRISIONERS!!! ALL SINGLETONS MUST DIE !!!"

  11. "Dont call us, we call you" The Hollywood Principle

  12. Data Source Controller Service External RestAPI Error Tracker Logger Data

    Parser Cache Colaborates with
  13. Logger Controller Service DataSource DataParser ErrorTracker Cache External API Direct

    Acyclic Graph
  14. Dependencies Chain Controller A Dependencies Chain Controller B Dependencies Chain

    Controller N Application …
  15. WHO BUILDS THE OBJECT GRAPH ???

  16. Usually the application itself bootstraps the dependencies graph at launch

    time
  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
  18. Dependency Injection and Dependency Inversion are two distinct concepts, but

    related in practice
  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"
  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 } }
  21. Dependencies Chain App Controller Logger Instances Factory

  22. Dependencies Chain App Controller Instances Factory SL4JLogger Factory.createLogger( )

  23. None
  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
  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 ...
  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
  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
  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
  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
  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
  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
  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" }
  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" }
  34. bind<OkHttpClient>() with singleton { OkHttpClient.Builder().build() } bind<RestCaller>() with provider {

    RestCaller(httpClient = instance()) }
  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
  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
  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
  38. fun main(args: Array<String>) { val gateway: APIGateway by Injector.graph.instance() gateway.start()

    }
  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
  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 … }
  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
  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
  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
  44. None
  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
  46. FINAL
 REMARKS

  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
  48. https://speakerdeck.com/ubiratansoares

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

    Google Developer Expert for Android Teacher, speaker, etc, etc
  50. THANK YOU @ubiratanfsoares ubiratansoares.github.io https://br.linkedin.com/in/ubiratanfsoares