$30 off During Our Annual Pro Sale. View Details »

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)

Ubiratan Soares
PRO

January 11, 2018
Tweet

More Decks by Ubiratan Soares

Other Decks in Programming

Transcript

  1. Dependency Injection
    with KODEIN
    Ubiratan Soares
    January / 2017

    View Slide

  2. DI 101

    View Slide

  3. View Slide

  4. Dependency Injection is
    all about a separation of
    concerns between some
    object creation and usage

    View Slide

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

    View Slide

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

    View Slide

  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 { }

    View Slide

  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 { }

    View Slide

  9. Enforcing DI means that
    any class with an available
    public constructor gets all
    its collaborators at
    instantiation time

    View Slide

  10. "NO MERCY !!!
    NO PRISIONERS!!!
    ALL SINGLETONS
    MUST DIE !!!"

    View Slide

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

    View Slide

  12. Data
    Source
    Controller
    Service
    External
    RestAPI
    Error
    Tracker
    Logger Data
    Parser
    Cache
    Colaborates with

    View Slide

  13. Logger
    Controller
    Service
    DataSource
    DataParser
    ErrorTracker
    Cache
    External API
    Direct
    Acyclic
    Graph

    View Slide

  14. Dependencies
    Chain
    Controller A
    Dependencies
    Chain
    Controller B
    Dependencies
    Chain
    Controller N
    Application

    View Slide

  15. WHO BUILDS THE
    OBJECT GRAPH
    ???

    View Slide

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

    View Slide

  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

    View Slide

  18. Dependency Injection
    and
    Dependency Inversion
    are two distinct concepts,
    but related in practice

    View Slide

  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"

    View Slide

  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
    }
    }

    View Slide

  21. Dependencies
    Chain
    App
    Controller
    Logger
    Instances Factory

    View Slide

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

    View Slide

  23. View Slide

  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

    View Slide

  25. object Injector {
    val graph = Kodein.lazy {
    bind() with singleton { Gson() }
    bind() with singleton {
    OkHttpClient.Builder().build()
    }
    bind() with provider {
    RestCaller(httpClient = instance())
    }
    // More bindings ...

    View Slide

  26. object Injector {
    val graph = Kodein.lazy {
    bind() with singleton { Gson() }
    bind() with singleton {
    OkHttpClient.Builder().build()
    }
    bind() with provider {
    RestCaller(httpClient = instance())
    }
    // More bindings ...
    The inferred
    LazyKodein subtype
    represents
    the object graph

    View Slide

  27. object Injector {
    val graph = Kodein.lazy {
    bind() with singleton { Gson() }
    bind() with singleton {
    OkHttpClient.Builder().build()
    }
    bind() with provider {
    RestCaller(httpClient = instance())
    }
    // More bindings ...
    Instances will be
    created in a lazy way

    View Slide

  28. object Injector {
    val graph = Kodein.lazy {
    bind() with singleton { Gson() }
    bind() with singleton {
    OkHttpClient.Builder().build()
    }
    bind() with provider {
    RestCaller(httpClient = instance())
    }
    // More bindings ...
    Defines a binding,
    eg, a way to provide
    an object instance

    View Slide

  29. object Injector {
    val graph = Kodein.lazy {
    bind() with singleton { Gson() }
    bind() with singleton {
    OkHttpClient.Builder().build()
    }
    bind() with provider {
    RestCaller(httpClient = instance())
    }
    // More bindings ...
    Defines the lifecycle time
    - or scope - of this object

    View Slide

  30. object Injector {
    val graph = Kodein.lazy {
    bind() with singleton { Gson() }
    bind() with singleton {
    OkHttpClient.Builder().build()
    }
    bind() with provider {
    RestCaller(httpClient = instance())
    }
    // More bindings ...
    Pulls a dependency
    transitively from the graph,
    with a proper scope

    View Slide

  31. bind() with provider {
    APIGateway(
    homeService = instance(),
    brokerService = instance(),
    walletService = instance()
    )
    }
    You should use named
    parameters to clarify the
    dependencies that are being
    pulled transitively

    View Slide

  32. bind(UITHREAD) with singleton {
    AndroidSchedulers.mainThread()
    }
    bind(WORKER) with singleton {
    Schedulers.io()
    }
    bind() with provider {
    SomeInfrastructure(
    storage = instance(),
    webService = instance(),
    worker = instance(WORKER)
    )
    }
    companion object {
    val WORKER = "worker"
    val UITHREAD = "main"
    }

    View Slide

  33. You can bind multiple
    instances of same
    type using tags
    bind(UITHREAD) with singleton {
    AndroidSchedulers.mainThread()
    }
    bind(WORKER) with singleton {
    Schedulers.io()
    }
    bind() with provider {
    SomeInfrastructure(
    storage = instance(),
    webService = instance(),
    worker = instance(WORKER)
    )
    }
    companion object {
    val WORKER = "worker"
    val UITHREAD = "main"
    }

    View Slide

  34. bind() with singleton {
    OkHttpClient.Builder().build()
    }
    bind() with provider {
    RestCaller(httpClient = instance())
    }

    View Slide

  35. bind() with singleton {
    OkHttpClient.Builder().build()
    }
    bind() with provider {
    RestCaller(httpClient = instance())
    }
    This dependency will live
    as long the Kodein
    instance lives

    View Slide

  36. bind() with singleton {
    OkHttpClient.Builder().build()
    }
    bind() with provider {
    RestCaller(httpClient = instance())
    }
    Each time this
    dependency will be
    required, a new instance
    will be created

    View Slide

  37. You may have a dependency bounded with a
    custom scope, eg, this dependency will live as
    long the provided object lives
    bind() with scopedSingleton(androidActivityScope) {
    Presenter(
    scheduler = instance(UITHREAD),
    view = it
    )
    }
    The object itself is accessible at binding time

    View Slide

  38. fun main(args: Array) {
    val gateway: APIGateway by Injector.graph.instance()
    gateway.start()
    }

    View Slide

  39. fun main(args: Array) {
    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

    View Slide

  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()
    // Do your work …
    }

    View Slide

  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()
    // Do your work …
    }
    This interface defines
    an instance that can hold a
    dependency graph

    View Slide

  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()
    // Do your work …
    }
    Provided by
    the Android support

    View Slide

  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()
    // Do your work …
    }
    Provides the custom
    dependency scope

    View Slide

  44. View Slide

  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

    View Slide

  46. FINAL

    REMARKS

    View Slide

  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

    View Slide

  48. https://speakerdeck.com/ubiratansoares

    View Slide

  49. UBIRATAN
    SOARES
    Computer Scientist by ICMC/USP
    Software Engineer, curious guy
    Google Developer Expert for Android
    Teacher, speaker, etc, etc

    View Slide

  50. THANK YOU
    @ubiratanfsoares
    ubiratansoares.github.io
    https://br.linkedin.com/in/ubiratanfsoares

    View Slide