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