Ejemplos de cómo aplicar Inyección de Dependencias en Kotlin

Overview

Kotlin Inyección de Dependencias Ejemplos

Ejemplos de cómo aplicar Inyección de Dependencias en Kotlin

Kotlin LISENCE GitHub

imagen

Acerca de

El siguiente proyecto tiene como objetivo acercar cómo usar la Inyección de Dependencias en Kotlin ya sea de manera manual o usando librerías como Dagger y Koin.

Inyección de Dependencias (DI)

imagen2

La inyección de dependencias es una técnica de desarrollo que permite a los desarrolladores de software, a través de la inyección de dependencias, obtener una dependencia de una clase en una clase que no tiene acceso a ella.

El Principio de inyección de dependencia no es más que poder pasar (inyectar) las dependencias cuando sea necesario en lugar de inicializar las dependencias dentro de la clase receptora y con ello poder desacoplar la construcción de sus clases de la construcción de las dependencias de sus clases.

Es decir, aplicamos una composición entre clases, con el objetivo que cada clase tenga sus responsabilidades bien definidas y acotadas. Es decir, si una clase A, necesita alguna funcionalidad de B, nosotros al crear A, debemos "inyectarle" B. De esta manera A, puede usar la funcionalidad de B.

De esta manera, podemos cambiar B, por C, siempre y cuando mantengan el contrato que permite ser usado por A. Ya no es la clase A la responsable de definir sus dependencias sino que lo es el programa o clase superior que le inyecta la dependencia que en ese momento necesite según los requerimientos.

Código Acoplado

Esto es lo que no deberíamos hacer

class ClassA {

  var classB = ClassB()

  fun tenPercent() {
    return classB.calculate() * 0.1d
  }
}
fun main() {
    val classA = ClassA()
}

Inyección por Setter

No recomendado. Con este enfoque, eliminamos la palabra clave new ClassB de nuestra ClassA. Por lo tanto, alejamos la responsabilidad de la creación de ClassB deClassA.

class ClassA {

  var lateinit classB: ClassB

  /* Setter Injection */
  fun setClassB(injected: ClassB) {
    classB = injected
  }

  fun tenPercent() {
    return classB.calculate() * 0.1d
  }
}
class Main {
  fun main() {
    val classA = ClassA()
    val classB = ClassB()

    classA.setClassB(classB)

    println("Ten Percent: ${classA.tenPercent()}")
  }
}

Pero hay un problema significativo con el enfoque de Inyección con Setters:

Estamos ocultando la dependencia ClassB enClassA porque al leer la firma del constructor, no podemos identificar sus dependencias de inmediato. El siguiente código provoca una NullPointerException en tiempo de ejecución:

class Main {
  fun void main() {
    val classA = ClassA()

     println("Ten Percent: ${classA.tenPercent()}") // NullPointerException here
  }
}

Inyección con Constructor

ClassA todavía tiene una fuerte dependencia de ClassB pero ahora se puede inyectar desde afuera usando el constructor:

class ClassA(val classB: ClassB) {

  int tenPercent() {
    return classB.calculate() * 0.1d
  }
}
class Main {
  fun main() {
    /* Notice that we are creating ClassB fisrt */
    val classB = ClassB()

    /* Constructor Injection */
    val classA = ClassA(classB)

    println("Ten Percent: ${classA.tenPercent()}")
  }
}

La funcionalidad permanece intacta en comparación con el enfoque de Inyección Setter. Eliminamos la inicialización nueva de la ClaseA.

Todavía podemos inyectar una subclase especializada de ClassB a ClassA.

Ahora el compilador nos pedirá las dependencias que necesitamos en tiempo de compilación.

Inyección de dependencias IoC o Service Locator

image image image

A la hora de resolver las dependencias veremos que tendremos dos enfoques, uno puro, basado en un contenedor de DI, grafo de dependencias o módulo de Inversión de Control y otros enfoques que es a través de un proveedor de servicios.

La inyección de dependencia es una técnica en la que un objeto recibe otros objetos de los que depende. Estos otros objetos se denominan dependencias..

El patrón de localización de servicios es un patrón de diseño utilizado en el desarrollo de software para encapsular los procesos involucrados en la obtención de un servicio con una fuerte capa de abstracción. Este patrón utiliza un registro central conocido como “localizador de servicios”, que a pedido devuelve la información necesaria para realizar una determinada tarea.

Service Locator se utiliza cuando no conoce el proveedor real del servicio antes del tiempo de ejecución. DI se usa cuando sabe que es el contenedor estático el que proporciona ese servicio.

En resumen, el Localizador de servicios y la Inyección de dependencias son solo implementaciones del Principio de inversión de dependencias.

Ambos suenan similares y nos brindan beneficios similares, pero en algún lugar te preguntas por qué tenemos dos nombres para el mismo patrón que hace un trabajo casi similar.

La diferencia puede parecer leve aquí, pero incluso con Service Locator, la clase sigue siendo responsable de crear sus dependencias. Simplemente usa el localizador de servicios para hacerlo. Le pide a ServiceLocator que obtenga sus dependencias. Con la inyección de dependencia, la clase recibe sus dependencias. No sabe ni le importa de dónde vienen.

Cómo seguir este proyecto

Hay distintos problemas tipo que resolveremos de manera manual, con Dagger2 y con Koin. Deberías mirar las tres implementaciones del mismo proyecto. Se ha intentado hacer los menores cambios posibles de la implementación base para que puedas ver cómo se puede hacer.

El orden para echarles un ojo es:

  • Casas: dependencias para tener una casa con puertas y ventanas.
  • Cafeteras: cómo tener una cafetera en base a su bomba y calentador y realizar un café.
  • Personas: Ejemplo típico de un MVC, es decir, Modelo, Servicios, Repositorios y Controladores.
  • MyView: o como inyectar a una vista compuesta por un presentador y un navegador.

Inyección de dependencias manual

En estos ejemplos, se muestra distintos tipos de inyecciones, ya sea usando clases o aplicando el patrón de inyección en base a interfaces.

Se implementan desde constructores o builders que las obtienen en base a una función de inyección, a construcción de las dependencias de manera "perezosa" o lazy, con el objetivo de que la dependencia solo se cargue la primera vez que se ejecute.

Inyección de dependencias con Dagger2

diagrama

Es un Framework creado inicialmente por Square y actualmente mantenido por Google para aplicaciones Java/Kotlin y Android cuyo principal objetivo es facilitar la implementación del patrón de diseño de Inyección de Dependencias, en otras palabras, se busca que sea Dagger2 el responsable de crear y administrar la creación de objetos en toda la aplicación. La inyección por dependencias hace que el proceso de inyección más automatizada , pero a la vez complicada de seguir/trazar.

ImagenDagger

@Singleton 
class Something @Inject constructor() {
   //... 
}
@Singleton 
class OtherThing @Inject constructor() {
   //... 
}
@Singleton
class Dependency @Inject constructor(
    something: Something,
    otherThing: OtherThing) {
   // ... Do something
}
class Target {
   lateinit var dependency: Dependency
}

Dagger resuelve las dependencias usando anotaciones, y generando las clases necesarias para la inyección de dependencias. El procesamiento de anotaciones requiere un tiempo de compilación adicional para generar dicho código. A veces, los cambios no se reflejan en la recompilación y requieren una limpieza del proyecto para regenerar código nuevo.

Podemos resumir el funcionamiento de Dagger2 en el siguiente diagrama:

daggerEsquema

Tendremos un Proveedor, es el encargado de definir cómo se construyen las dependencias. En Dagger2 utilizamos Módulos y cada módulo es una clase que tiene el manejo de la creación de dichas dependencias.

En consecuencia tenemos un Consumidor, quien es el que necesita de ciertas dependencias que solicitará al ** Proveedor** por medio de un Facilitador.

Y muy importante el Facilitador, que utiliza Componentes, los cuáles se encargan de permitir el acceso a las dependencias creadas para su uso a los Consumidores. Dagger2 es quien se encarga mayoritariamente de implementar esta parte del Framework.

Las siguientes anotaciones le permiten a Dagger2 identificar a través de toda la aplicación qué, cómo y dónde debe realizar la inyección de dependencias:

  • @Module Identifica qué clases son las encargadas de construir dependencias. Se indica como anotación arriba de la clase. Será Proveedores de dependencias.
  • @Provides Utilizado dentro de una clase con anotación @Module para indicar individualmente el objeto que provee una dependencia. Se indica como anotación arriba de un método. Lo usaremos para implementar casos concretos, o con librerías de terceros.
  • @Bind Utilizado dentro de una clase con anotación @Module para indicar individualmente el objeto que provee una dependencia. Se utiliza cuando sabemos que son interfaces y no se necesita una implementación concreta de los mismos. Para ello nuestro Modulo debe ser una interfaz o clase abstracta. Importante Si usas @Binds, estás obligado a poner @Inject en el constructor de clase de la dependencia, para que sepa como se crea (esto no tiene que ser así con @Provides).
  • @Component Indica cuales son las dependencias que van a estar a disposición de los Consumidores a través de Módulos u Objetos. Se indica como anotación arriba de una interfaz.
  • @Inject Dentro del Consumidor (Activity, Fragment, otra clase) se indica ya sea en un Miembro (atributo, campo), función o constructor de la clase, y permite identificar las dependencias que van a ser inyectadas. * Importante* Si usas @Binds, estás obligado a poner @Inject en el constructor de clase de la dependencia, para que sepa como se crea (esto no tiene que ser así con @Provides, pero si lo pones nunca te equivocas).
  • @Singleton Si deseamos que las instancias que nos proporciona Dagger 2 sea Singleton bastará con anotar la clase o el método @Provides/@Binds con @Singleton. En el primer caso, siempre que lo necesitemos, devolverá el mismo objeto. En el segundo caso, solo lo tratará así en el módulo donde generemos la dependencia. Te recomiendo usarlo con @Binds
  • @Named En ocasiones necesitaremos inyectar distintas implementaciones de un interface por lo que usaremos varios métodos @Provides anotándolos con @Named.
  • Lazy Si el coste de instanciar un objeto es alto y no siempre se llega a utilizar, podemos indicar su instanciación como Lazy y no se creará hasta la primera vez que se utilice, para usarlo debemos usar get() en el método que lo utiliza.
  • Provider En ocasiones queremos una instancia nueva del objeto cada vez que la utilicemos. Para ello usamos un Provider en el atributo que queramos. Lo recuperaremos con get().

Más información en: https://dagger.dev/

Inyección de dependencias con Koin

imageKoin

Koin es un framework de inyección de dependencias pragmático y liviano para desarrolladores Kotlin. Técnicamente Koin es un Service Locator. La idea básica detrás de un Service Locator es tener una clase que sepa cómo obtener todos los servicios que utiliza nuestra aplicación. Así que, el Service Locator tendría una propiedad por cada uno de esos servicios, que devolvería un objeto del tipo adecuado cuando se lo soliciten. Service Locator garantiza que el desarrollador obtenga lo solicitado automáticamente, introduzca un poco más de código, pero luego facilite la trazabilidad.

ServiceLocator

class Something {
    //...
}

class OtherThing() {
    //...
}
class Dependency(
    something: Something,
    otherThing: OtherThing) {
    // ... Do something
}
val mainKoinModule =
    module {
        single { Something() }
        single { OtherThing() }
        single { Dependency(get(), get()) }
    }
class Target {
   private val dependency: Dependency by inject()
}

El principal secreto de Korin es usar los Reified Functions, es decir, reificar la información de tipo genérico en tiempo de ejecución. Además basado en DSL (Domain Specific Language) otras de las características de usar Kotlin.

Para trabajar con Koin debemos manejar estos conceptos:

  • Funciones:

    • startKoin { } Crea una instancia de Koin y registra su contexto.
    • logger() Carga el logger a usar por Koin, si necesitamos de ello.
    • modules() Carga la lista de módulos que va a usar Koin.
    • by inject() Obtiene la dependencia de manera perezosa o lazy.
    • get() Obtiene la dependencia de manera directa, es decir, la instancia.
    • getProperty()/setProperty() Getter/Setter de una propiedad.
    • KoinComponent { } Te permite usar las facilidades de Koin.
  • Scope:

    • module { } Crea el módulo que Koin usa para proveer todas las dependencias.
    • factory { } Nos ofrece una instancia nueva del objeto cada vez que se produzca la inyección.
    • single { } Nos ofrece la dependencia como singleton, es decir, siempre la misma instancia del objeto cada vez que sea inyectada.
    • get() Es usado en el constructor o en otros contextos para proveer las dependencias indicadas.
    • scope { } Grupo logico para el scope
    • scoped { } Ofrece la definición de una dependencia activa un contexto, o scope
  • Modulos:

    • named("a_qualifier") Ponemos un texto a la definición para "cualificarlo".
    • named() Devuelve un tipo a partir de una "definición" dada.
    • bind() Indica el tipo de dependencia se va a hacer el bind con el objeto.
    • binds(arrayOf(...)) Indica un array de tipos se va a hacer el bind con el objeto.
    • createdAtStart() Crea una instancia de Koin del tipo Singleton al comienzo.

Por otro lado, Koin también te deja trabajar con anotaciones, lo que le da un efoque muy rápido cómo definimos las dependencias.

Más información en: https://insert-koin.io/

Conclusiones

Es importante no obsesionarse en si la inyección se resuelve por anotaciones, por DSL o si realmente las librerías que usas son un sistema DI puro o basado en un Service Locator (no te vuelvas loco/a por eso ni seas tan purista, lo importante es que las dependencias te las da). Mi consejo es que uses el que más seguro te haga sentir y sobre todo el que se adapte mejor a tu problema o aplicación de desarrollar.

Debemos tener en cuenta que Dagger2 es la opción recomendada por Google en Android y que Hilt se basa en ella, pero esto no quiere decir que no uses otras.

Dagger2 y su sistema de anotaciones, hace que la compilación sea más lenta, pero debas limpiar el proyecto y generar las clases. Por otro lado puede ser a veces un poco más complejo. Es ideal para proyectos que crecen en tamaño y necesites tener tipado todo. Se basa en el procesamiento de anotaciones para generar una gran cantidad de código repetitivo en tiempo de compilación. El código generado incluye factorias para todas sus clases, de modo que puede crear instancias de ellas sin preocuparse por sus dependencias.

Koin es un service locator, pero que te resuelve el problema y de manera muy óptima. Tiene elementos muy interesante y funciona a la perfección con Kotlin. Puedes nombrar dependencias y puedes aplicar Lazy de la misma manera que lo hace Kotlin y no cargando una librería especial. Para medianos o pequeños es una opción muy recomendada. Koin usa DSL de Kotlin y resuelve de forma perezosa sus dependencias en tiempo de ejecución. No hace nada en tiempo de compilación. Es una biblioteca mucho más pequeña y liviana que Dagger, y no genera ningún código. Su problema es que a veces puede dar los errores en tiempo de ejecución, al no ser compilado como Dagger2.

A nivel de rendimiento, dado que Dagger hace todo su trabajo en tiempo de compilación, tiene el mejor rendimiento en tiempo de ejecución en todos los dispositivos, a costa de tiempos de compilación más largos. Koin es más lento que Dagger, pero la diferencia en el tiempo de configuración e inyección no se nota mucho cuando se usa una aplicación.

A la hora de testear, con Dagger2 puedes proporcionar fácilmente versiones simuladas (mocks) de sus clases configurando un TestComponent para usar en sus pruebas. TestComponent debe extender la clase de componente de producción normal. Puedes incluir módulos de producción o módulos de prueba simulados. Dado que Koin resuelve las dependencias de forma perezosa en el tiempo de ejecución, no sabrá que hay un problema hasta que active la línea específica de código erróneo y la aplicación se bloquee. Pero una vez que se encuentre con la excepción, podrá ver un seguimiento de la pila y saber exactamente qué línea la causó. Los mensajes de error de excepción de Koin son bastante comprensibles y descriptivos (de lo mejor).

Más información: https://proandroiddev.com/exploring-dependency-injection-in-android-dagger-koin-and-kodein-e219a764be52

Autor

Codificado con 💖 por José Luis González Sánchez

Twitter GitHub

Contacto

Cualquier cosa que necesites házmelo saber por si puedo ayudarte 💬 .

        

Licencia

Este proyecto está licenciado bajo licencia MIT, si desea saber más, visite el fichero LICENSE para su uso docente y educativo.

You might also like...
Integration Testing Kotlin Multiplatform Kata for Kotlin Developers. The main goal is to practice integration testing using Ktor and Ktor Client Mock
Integration Testing Kotlin Multiplatform Kata for Kotlin Developers. The main goal is to practice integration testing using Ktor and Ktor Client Mock

This kata is a Kotlin multiplatform version of the kata KataTODOApiClientKotlin of Karumi. We are here to practice integration testing using HTTP stub

Small kotlin library for persisting _single instances_ of kotlin data classes
Small kotlin library for persisting _single instances_ of kotlin data classes

PerSista Small library for persisting single instances of kotlin data classes. NB: PerSista uses typeOf() internally which is marked as @ExperimentalS

Kotlin Leaning Notes from Udacity Course | Kotlin Bootcamp for Programmers by Google
Kotlin Leaning Notes from Udacity Course | Kotlin Bootcamp for Programmers by Google

Kotlin Beginners Notes These are all personal notes taken from the Udacity Course (ud9011) of Kotlin Bootcamp for Programmers by Google as well as oth

Saga pattern implementation in Kotlin build in top of Kotlin's Coroutines.

Module Saga Website can be found here Add in build.gradle.kts repositories { mavenCentral() } dependencies { implementation("io.github.nomisr

Kotlin microservices with REST, and gRPC using BFF pattern. This repository contains backend services. Everything is dockerized and ready to
Kotlin microservices with REST, and gRPC using BFF pattern. This repository contains backend services. Everything is dockerized and ready to "Go" actually "Kotlin" :-)

Microservices Kotlin gRPC Deployed in EC2, Check it out! This repo contains microservices written in Kotlin with BFF pattern for performing CRUD opera

A sample skeleton backend app built using Spring Boot kotlin, Expedia Kotlin Graphql, Reactive Web that can be deployed to Google App Engine Flexible environmennt

spring-kotlin-gql-gae This is a sample skeleton of a backend app that was built using: Spring Boot(Kotlin) Reactive Web Sprinng Data R2DBC with MYSQL

Modular Android architecture which showcase Kotlin, MVVM, Navigation, Hilt, Coroutines, Jetpack compose, Retrofit, Unit test and Kotlin Gradle DSL.

SampleCompose Modular Android architecture which showcase Kotlin, MVVM, Navigation, Hilt, Coroutines, Jetpack compose, Retrofit, Unit test and Kotlin

Learn-kotlin - Learning more about Kotlin in various content

Kotlin study roadmap https://kotlinlang.org/docs/reference/ Getting Started Basi

Mis experimentos con Kotlin para JetBrains Academy, certificación de Kotlin donde voy resolviendo proyectos de evaluación y haciendo actividades de cada tema.
Mis experimentos con Kotlin para JetBrains Academy, certificación de Kotlin donde voy resolviendo proyectos de evaluación y haciendo actividades de cada tema.

Kotlin Academy Mis experimentos con Kotlin para JetBrains donde voy resolviendo proyectos de evaluación y haciendo actividades de cada tema. Acerca de

Owner
José Luis González Sánchez
Dr. en Informática (PhD). Profe de Secundaria en DAM/DAW/ASIR. Soy GitHub Campus Advisor y Embajador de GitKraken. Tenis, rock, guitarra y buenos momentos.
José Luis González Sánchez
Jugando con un Parking realizado como prueba de examen en JetBrains Academy, curso Kotlin Developer

Kotlin Parking Lot Proyecto de evaluación para el título de Kotlin Developer en Jetbrains Academy. Consiste en realizar un parking con las especificac

José Luis González Sánchez 1 Jan 10, 2022
Ejemplo de cómo trabajar con DataFrames similar a Pandas (python) en Kotlin.

Kotlin DataFrames Pandas Ejemplo de cómo trabajar con DataFrames similar a Pandas (python) en Kotlin. Kotlin DataFrames Pandas Acerca de Autor Contact

José Luis González Sánchez 2 Jul 24, 2022
Ejemplo de cómo trabajar con gráficos en Kotlin

Kotlin Let's Plot Ejemplo de cómo trabajar con gráficos en Kotlin. Kotlin Let's Plot Acerca de Autor Contacto ¿Un café? Licencia de uso Acerca de Este

José Luis González Sánchez 12 Oct 11, 2022
Este es un ejemplo de como usar Kotlin en un proyecto Node.js, una api rest en Node.js con Express

Kotlin Hello Node.js Este es un ejemplo de como usar Kotlin en un proyecto Node.js, una API REST con Express Kotlin Hello Node.js Acerca de Uso Compil

José Luis González Sánchez 4 Jul 16, 2022
Exemplo de microsserviço em koltin e spring boot usando a arquitetura hexagonal/ports&adapters como base de estrutura

Microservice boilerplate Exemplo de microsserviço em koltin e spring boot usando a arquitetura hexagonal como base de estrutura Executando Localmente

Odilio 6 Dec 7, 2022
Repo: Programming problems with solutions in Kotlin to help avid Kotlin learners to get a strong hold on Kotlin programming.

Kotlin_practice_problems Repo: Programming problems with solutions in Kotlin to help avid Kotlin learners to get a strong hold on Kotlin programming.

Aman 0 Oct 14, 2021
Mocking for Kotlin/Native and Kotlin Multiplatform using the Kotlin Symbol Processing API (KSP)

Mockative Mocking for Kotlin/Native and Kotlin Multiplatform using the Kotlin Symbol Processing API (KSP). Installation Mockative uses KSP to generate

Mockative 121 Dec 26, 2022
Kotlin-oop - Repositório criado para ser utilizado pelo projeto de Kotlin OOP desenvolvido em Kotlin nas aulas feitas através da plataforma Alura.

Projeto React OOP Repositório criado para ser utilizado pelo projeto de Kotlin OOP desenvolvido em Kotlin nas aulas feitas através da plataforma Alura

Marcos Felipe 1 Jan 5, 2022
Kotlin-koans - Kotlin Koans are a series of exercises to get you familiar with the Kotlin Syntax

kotlin-koans-edu Kotlin Koans are a series of exercises to get you familiar with

null 1 Jan 11, 2022
Kotlin TodoMVC – full-stack Kotlin application demo

Kotlin full stack TodoMVC This project is an example implementation of the TodoMVC app written in Kotlin. More specifically, it's the Kotlin port of t

Gyula Voros 22 Oct 3, 2022