# Toxin

`Toxin` это kotlin-first библиотека для работы с зависимостями в многомодульных приложениях. Поводом для написания стали проблемы с `Dagger 2` (время компиляции и сложности модуляризации) и отсутствие альтернатив с валидацией графа и без кодогенерации. Многие концепции библиотеки совпадают с `dagger 2`. За идею для построения api был взят `koin`, однако api `toxin` более консистентное (иногда менее удобное из-за необходимости валидировать граф).

## Ключевые особенности
* **Отсутствует кодогенерация**
* **Граф валидируется из коробки**
* **Граф иммутабельный**
* **Высокая производительность**
* **Легковестность (несколько классов)**
* **Удобный kotlin-dsl**
* **Удобная мультимодульность**
* **Произвольная структура графа**

{% cut "TL;TD пример А до Я" %}

```kotlin
fun myFeatureModule() = module {

    singleton<MyFeatureContext> {
        MyFeatureContext()
    }

    factory<MyFeatureUseCases> {
        MyFeatureUseCases(myFeatureContext = get())
    }

    factory<MyFeatureFormatter> {
        MyFeatureFormatter()
    }

}

fun myFeatureScope() = scope("my-feature") {
    useModule(myFeatureModule())
}

class MyFeatureFragment : Fragment() {

    private val dependencies by inject(MyFeatureFragmentInjector())

    class Dependencies(
        val presenter: MyFeaturePresenter
    )
}

class MyFeatureFragmentInjector : Injector<MyFeatureFragment.Dependencies>(myFeatureScope()) {

    override fun invoke() = assisted {
        MyFeaturePresenter(
            useCases = get(),
            formatter = get()
        )
    }
}
```

{% endcut %}

## Scope
* Библиотека `toxin` позволяет описывать произвольное количество графов зависимостей.
* Каждый граф зависимостей представлен объектом типа `Scope`.
* Объект типа `Scope` в свою очередь может зависеть от других `Scope`, что позволяет создавать графы с большим количеством зависимостей из небольших подграфов.
* Каждый `Scope` требует указать произвольную строку при создании для того, чтобы в случае ошибки можно было локализовать граф, в котором произошла ошибка.
* Метод создания `Scope` может содержать параметры, необходимые для описания `Scope`.
* `Scope` отвечает за хранение экземпляров созданных типов (singleton, reusable).
```kotlin
// Пример описания скоупа

fun myFeatureScope() = scope("my-feature") {
    dependsOn(coreScope)
    useModule(myFeatureModule())
}
```

## Module
* Модули отвечают за группировку фабрик для создания объектов.
* Чтобы научить `Scope` создавать определенный тип, при его описании нужно указать `Module`, в котором есть фабрика для данного типа.
* Описание модуля может использовать внешние типы, для того чтобы их передать используйте параметры метода, который создает модуль.
```kotlin
// Пример описания модуля

fun myFeatureModule(context: Context) = module {

    factory<MyType> {
        ..
    }

    singleton<MyFeatureContext> {
        ..
    }

}
```

{% note tip %}

Модуль не отвечает за хранение созданных экземпляров, хранить его не нужно.

{% endnote %}

## Object Factory
* В `toxin` есть несколько типов фабрик для создания экземпляров объектов.
* Фабрика `factory<T>` не кеширует экземпляры, при каждом обращении будет создан новый объект. Используйте данный тип фабрики по умолчанию.
* Фабрика `singleton<T` возвращает один и тот же экземпляр в рамках одного экземпляра `Scope`. Используйте данный тип фабрики, если вам нужно обеспечить уникальность экземпляра типа в рамках `Scope`.
* Фабрика `reusable<T>` может возвращать один и тот же экземпляр в рамках одного `Scope`. Используйте данный тип фабрики, если вам нужно предоставлять экземпляр типа большому количеству потребителей.
* Для создания типа фабрике доступны экземпляры любых типов из `Scope`, частью которого он является.
```kotlin
// Пример описания фабрики

factory<MyType> {
    MyType(myFeatureContext = get())
}
```

{% note tip %}

Чтобы найти фабрику конкретного типа используйте полнотекстный поиск, указав `<MyType>` в качестве текста для поиска.

{% endnote %}

## Collection Factory
* В `toxin` поддержаны мульти-биндинги (или коллекции зависимостей).
* Фабрика `collect<T>` позволяет объявить элемент коллекции типа `List<T>`.
* Кеширование экземпляров элементов коллекции остается на стороне потребителя.
```kotlin
// Пример описания фабрики

collect<MyType> {
    MyType(myFeatureContext = get())
}
```

{% note tip %}

Мульти-биндинги позволяют собирать реализации интерфейсов или абстрактных классов со всех модулей приложения.

{% endnote %}

## Creation DSL
* Для описания способа создания объекта `toxin` предоставляет kotlin-dsl, который доступен внутри фабрик.
* Метод `get<T>()` пытается найти указанный тип в текущем `Scope` и выбрасывает ошибку, если этого сделать не удалось.
* Метод `opt<T>()` пытается найти указанный тип в текущем `Scope` и возвращает `null`, если этого сделать не удалось.
* Метод `getLazy<T>()` возвращает `Lazy<T>` для указанного типа (наличие фабрики для типа не проверяется, ошибка произойдет в момент обращения к `Lazy`).
* Метод `optLazy<T>()` возвращает `Lazy<T?>` для указанного типа (если не удалось найти фабрику для типа, при обращении к `Lazy` вернется `null`).
* Метод `getProvider<T>()` возвращает `Provider<T>` для указанного типа (наличие фабрики для типа не проверяется, ошибка произойдет в момент обращения к `Lazy`).
* Метод `optProvider<T>()` возвращает `Provider<T?>` для указанного типа (если не удалось найти фабрику для типа, при обращении к `Lazy` вернется `null`).
* Метод `collect<T>` возвращает `List<T>` список элементов указанного типа и выбрасывает ошибку, если нет ни одного элемента указанного типа.
* Метод `collectOrEmpty<T>` возвращает `List<T>` список элементов указанного типа и пустой список, если нет ни одного элемента указанного типа.
* Метод `collectionLazy<T>` возвращает `Lazy<List<T>` для списка с элементами указанного типа (наличие элементов не проверяется, ошибка произойдет в момент обращения к `Lazy`).
* Метод `collectionProvider<T>` возвращает `Provider<List<T>` для списка с элементами указанного типа (наличие элементов не проверяется, ошибка произойдет в момент обращения к `Provider`).
* Так же при описании способа создания объекта можно использовать параметры, переданные в метод создания модуля.
```kotlin
// Пример описания способа создания объекта

factory<MyType> {
    MyType(
        optionalDependency = opt(),
        heavyDependency = getLazy(),
        items = collect()
    )
}
```
{% note warning %}

Инициализация объекта не должна содержать условной логики, поскольку это затрудняет валидацию графа.

{% endnote %}

## Component
* Объект типа `Component` отвечает за доступ к экземплярам типов из указанного `Scope`.
* Объект типа `Scope` не имеет публичного api для получения зависимостей.
* `Scope` должен однозначно определяется для компонента в момент инициализации (это необходимо для валидации графа).
* Метод `auto<T>` внутри компонента позволяет получить экземпляр типа из указанного `Scope` (если тип создать не удалось, метод выбросит ошибку).
* Метод `autoOrNull<T>` внутри компонента позволяет получить экземпляр типа из указанного `Scope` (если тип создать не удалось, метод вернет `null`).
* Метод `assisted<T>` внутри компонента позволяет получить доступ к `Creation DSL` и описать фабрику для создания необходимого типа.
* Метод `collection<T>` внутри компонента позволяет получить список из элементов данного типа (если нет элементов, метод выбросит ошибку).
```kotlin
// Пример описания компонента

class MyFeatureComponent(context: Context) : Component(myFeatureScope(context)) {

    fun glideRequestManager() = auto<RequestManager>()

    fun myFeaturePresenter(myEntityId: String) = assisted {
        MyFeaturePresenter(
            featureContext = get(),
            entityId = myEntityId
        )
    }
}
```

{% note tip %}

Конструктор компонента и метод получения экземпляра типа могут содержать параметры, необходимые для доконфигурации типа.

{% endnote %}

## Injector (experimental)
* Объект типа `Injector` позволяет внедрять зависимости в типы, создание которых не удается вынести в `scope` (`Activity`, `Fragment`, `Service` и т.д.).
* Тип `Injector` является компонентом с ограниченным api.
* Тип `Injector` должен реализовывать единственный метод, создающий контейнер для зависимостей.
* Для работы с `Injector` используйте метод `inject(myInjector)` внутри типа, в который хотите внедрить зависимости.
```kotlin
// Пример использования инжектора

class MyFragment : Fragment() {
    private val dependencies by inject(MyFragmentInjector())

    class Dependencies(
        val d1: FirstDependency,
        val d2: SecondDependency
    )
}

class MyFragmentInjector : Injector<MyFragment.Dependencies>(myFeatureScope()) {

    override fun invoke() = assisted {
        MyFragment.Dependencies(
            d1 = get(),
            d2 = get()
        )
    }
}
```

## Валидация графа
Валидация графа зависимостей зависимостей возможна благодаря ряду ограничений, накладываемых на работу с `toxin`:
* Доступ к зависимостям должен осуществляться через методы объявленные внутри `Component`, что позволяет найти все точки обращения к графу.
* `Scope` статически задается при описании `Component`, что позволяет создавать компоненты через рефлексию в тесте.
* Интерфейс компонента не имеет `generic` сигнатур, что позволяет однозначно определять тип, который должен вернуть метод компонента.

**Реализованные проверки:**
* Проверка возможности создания всех декларируемых компонентами зависимостей - `MarketToxinTest`, метод `Check all component methods are resolvable`.
* Проверка корректности переопределений зависимостей при описании графа - `MarketToxinTest`, метод `Check all overrides are allowed`.

{% note tip %}

Если вы сильно изменили граф зависимостей, запустите руками `MarketToxinTest`. Это позволит быстрее найти и исправить ошибки.

{% endnote %}
