Inyección de Dependencias con KODEIN 

Jair Israel Avilés Eusebio

jairaviles.mx

@yajairo87

https://github.com/JairAviles

Marzo 2018

DI 101

Inyección de dependencias es todo aquello relacionado a la separación de la creación y utilización de objetos

 

class Player{
    fun throwDice(): Int {
        val r = Random()
        return r.nextInt(5) + 1
    }
    // do something else
}
class PlayerDI(private val r: Random) {

    fun throwDice(): Int {
        return.nextInt(5) + 1
    }
    // do something else

}

Sin inyección de dependencias

 

Con inyección de dependencias

 

¿Que se puede inyectar?

  • Business Managers
  • Presenters, Controllers, ViewModel
  • Repositories
  • OS services

¿Para que implemento DI?

  • Para hacer mi código mas testeable

Forzar DI significa que cualquier clase con un constructor publico definido obtenga las dependencias requeridas en tiempo de instanciación

Logger

Controller

Service

DataSource

Cache

 

DataParser

ErrorTracker

External

API

 

Controller

Service

DataSource

DataSource

ErrorTracker

External API

Logger

DataParser

Aplication

Controller A

Controller B

Controller N

Cadena

de Dependencias

Cadena

de Dependencias

Cadena

de Dependencias

...

¿Quién es responsable de construir ese grafo de objetos?

 

Usualmente la aplicación por si misma

arranca con las dependencias en el momento de lanzamiento de la aplicación

Static Factories

  • Definición manual de la creación de objetos y las relaciones del grafo
  • Código adicional necesario para las dependencias basadas en su scope
  • Reflection-free
  • Sin errores de DI en runtime
  • Boilerplate

JSR 330

  • Definición semiautomatizada de código por las anotaciones estandares
  • Reflection-based (Guice, Dagger1) o reflection-free(Dagger2)
  • Soluciones Reflection-based puede parar en tiempo de ejecución debido a la resolución de dependencias
  • Mucho menos código que los static factories

Inyección de Dependencias

Inversión de Dependencias

(No es lo mismo pero estan relacionados)

 

Con inyección de dependencias e

inversión de control

 

interface Dice{
    fun throw(): Int
}

class PlayerDIP(private val dice: Dice) {

    fun throwDice(): Int {
        return dice.throw()
    }
    // do something else
}

Aplication

Instancia de

Factories

 

Controller

Dice

Cadena de

Dependencias

Factory.createDice()

Resumen

  • 100% Framework de Kotlin para DI
  • Semantico ( un simple DSL)
  • Basado en características de Kotlin: type system y lazy properties
  • Reflection-based
  • Soporte para Java (legacy code)
  • Soporte para Kotlin-JVM, Android, JS y Kotlin-native

http://kodein.org/

¿Que tan estable es?

  • Versión 1.x (Abril 2015): Concepción del framework 
  • Versión 2.x (Octubre 2015): API robusta
  • Framework Injekt, DI se torna deprecated (Junio 2016)
  • Version 3.x (Julio 2016): Actualización del core y features adicionales
  • Version 4.x (Junio 2017): Compatible con JS
  • Version 5-beta (Actualmente): Compatible con Kotlin-Native, orientado a ser totalmente Lazy

Pero antes...

Unos definición de conceptos

Infix Functions

enum class ZodiacSign {
    ARIES, TAURUS, GEMINI, CANCER, LEO, VIRGO, LIBRA, SCORPIO, 
    SAGITTARIUS, CAPRICORN, AQUARIUS, PISCES
}

enum class Saint {
    MU, ALDEBARAN, SAGA, KANON, DEATHMASK, AIORIA, SHAKA, 
    DOHKO, MILO, AIOROS, SHURA, CAMUS, AFRODITA
}

data class GoldenSaint(val saint: Saint, val zodiacSign: ZodiacSign)

Invoco a Shaka de Virgo

val virgoGoldenSaint = GoldenSaint(Saint.SHAKA, ZodiacSign.VIRGO)

Infix Functions (cont...)

enum class Saint {
    MU, ALDEBARAN, SAGA, KANON, DEATHMASK, AIORIA, SHAKA, DOHKO, 
    MILO, AIOROS, SHURA, CAMUS, AFRODITA;

    fun of(zodiacSign: ZodiacSign) = GoldenSaint(this, zodiacSign)
}

val sagittariusGoldenSaint = Saint.AIOROS.of(ZodiacSign.SAGITTARIUS)

Invoco a Aioros de Sagitario

enum class Saint {
    MU, ALDEBARAN, SAGA, KANON, DEATHMASK, AIORIA, SHAKA, DOHKO, 
    MILO, AIOROS, SHURA, CAMUS, AFRODITA;

    infix fun of(zodiacSign: ZodiacSign) = GoldenSaint(this, zodiacSign)
}

val sagittariusGoldenSaint = AIOROS of SAGITTARIUS

Reified Generics

//El siguiente codigo generaria error, la JVM no tiene noción del tipo T (type erasure)
fun <T> instance() {
    //la JVM veria esto como any y no conseguiria definir el tipo T
    container.getInstance(T::class.java)
}

//La solución son los Reified types en conjunto con una inline function
inline fun <reified T> instance() {
    container.getInstance(T::class.java)
}

val m1 = instance<Manager>()

val m2 : Manager = instance() // Type inference!

Semántica en Kodein

object Injector {

    // Definición del grafo de objetos
    val kodein = Kodein.lazy {

        /*** Simple Bindings ***/

        //Define una dependencia singleton que se inicializa de forma Lazy
        bind<Gson()> with singleton { Gson() }
        bind<Dao>() with singleton { MongoDao() } // Esto es una infix function
        bind<OkHttpClient>() with singleton {
            OkHttpClient.Builder().build()
        }

         /*** Factory Bindings ***/

        //Define un bloque de dependencias  con argumentos
        //(nueva instancia cada llamado)
        bind<Service>() with factory { 
            tag: String -> Service(instance(), tag) 
        }

        //Similar al factory, solo que devuelve una unica instancia
        bind<Service>() with multiton { 
            tag: String -> Service(instance(), tag)
        }

    // ...      

}

Semántica en Kodein (cont...)

/*** Dependencia Transitiva ***/
    //Define un bloque de dependencias Factory 
    //sin argumentos (nueva instancia cada llamado)
    bind<RestCaller>() with provider {
      //Equivalente a usar @Inject - JSR 3330
      RestCaller(httpClient = instance(), scheduler = instance(WORKER)) 
    }

/*** Modulos ***/
    //Definido en otro lado
    val apiModule = Kodein.Module {
        bind<Whatever> with singleton { WhateverImpl(instance()) }
    }

    val kodein = Kodein {
        import(apiModule)
    }

/*** Binding Tags, es pasado como parametro en instance(MY_TAG) ***/
    bind<Scheduler>(UITHREAD) with singleton {
        AndroidSchedulers.mainThread()
    }

    bind<Scheduler>(WORKER) with singleton {
        Schedulers.io()
    }

    companion object {
        val WORKER = "worker"
        val UITHREAD = "main"
    }

Y muchas otras caracteristicas como:

 

  • Override (para tests)
  • KodeinInject (Proporciona un Inyector para acceder al grafo)
  • Eager Singletons
  • Soft/weak Singletons
  • Android sub scopes (utilizado para DI en fragments)
  • Custom scopes 

Ejemplo rápido

Star wars API (planets)

https://github.com/rafakob/KodeinSample

Conclusiones

  • Mas maneras de proveer y definir dependencias
  • Mas maneras de definir scopes de dependencias
  • Soporte a modulos, incluyendo overriding de dependencias (test)
  • Soporte multibinding
  • Interoperabilidad JSR330 para inyecciones
  • Excelente documentación

Referencias

  • Offical Github repository (http://kodein.org/)
  • The Kodein KOtlin DEpendency INjection library - Salomon Brys (https://youtu.be/OvqPGaj3C58 )
  • Kodein - Kotlin Dependency Injection. Orlando Android (https://youtu.be/wLOLALueDAo )
  • Baeldung - Kotlin Dependency Injection with Kodein (http://www.baeldung.com/kotlin-kodein-dependency-injection)

Gracias!

Inyección de Dependencias con KODEIN

By Jair Avilés

Inyección de Dependencias con KODEIN

  • 551