• Stars
    star
    231
  • Rank 173,434 (Top 4 %)
  • Language
    Kotlin
  • License
    Apache License 2.0
  • Created about 2 years ago
  • Updated 3 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Dependency Injection framework based on Google's Dagger2 API, optimized for fast builds and for managing large graphs with optional dependencies

Yatagan

Maven Central CI License

Yatagan is a fast Dependency Injection framework based on Google's Dagger2 API.

Yatagan is optimized for fast builds and supports managing large dynamic dependency graphs by introducing Conditions and Variants. It's primary goal is to improve build speed in large complex projects, which already use Dagger. Though it might be useful for others.

All core Dagger API is supported with little changes. Yet dagger-android, Hilt and a couple of less popular features are not supported. See comparative API reference for full info.

Yatagan can work in multiple modes (use different backends):

  • With code generation
    • APT/KAPT - classic mode.
    • KSP - leverages new Google KSP framework. Experimental, see KSP support status.
  • Via runtime Java reflection - a backend designed for fast local debug builds, see specific notes.

All backends are designed to be working identically, so one can easily switch backends depending on the working environment and requirements. Any inconsistencies between backends ought to be reported and fixed.

Motivation

One can consider migrating to Yatagan from vanilla Dagger if at least one of the points is true for their project:

  1. The project uses Dagger and has build performance issues because of it.
  2. The project needs/extensively uses dynamic optional dependencies in its DI graphs.

Yatagan tackles both of these issues.

Read more and dive into technical details in the Medium article.

Performance

As of the first point, performance gains can vary per project due to specific details and Yatagan usage configuration. Yatagan allows clients to apply processing to fewer project modules in comparison to Dagger. Yatagan processing should only be applied to project modules, that contain root components, and shouldn't be applied at all in case of reflection mode usage.

Thus, a project will have the biggest performance gain from using Yatagan, if the majority of the project modules have only one annotation processor - Dagger. Then upon migration to Yatagan project modules without root components can have kapt completely switched off with it remaining in root-component modules. Furthermore, root-component modules can also become kapt-free with Reflection mode. In a good scenario Yatagan can make incremental builds up to two times faster.

If other annotation processors besides Dagger are used throughout the project in KAPT mode, then performance gain from using Yatagan will be lower. One can try and reorganise the project to limit other annotation processors appliance to small modules or use them in KSP mode if supported. Reflection mode is also supported for some frameworks that feature code generation. It can be enabled in debug builds if this allows to eliminate KAPT from the project module.

The general idea is to remove KAPT from as many modules as possible, with large modules yielding more profit, so feel free to experiment with what Yatagan offers for this task.

In the worst case scenario, where using Yatagan doesn't remove KAPT from any of the modules, profits can still be around ~ 10% due to Yatagan optimizations.

Runtime conditions

The second point can be clarified by the following code snippet:

@Singleton
class MyClass @Inject constructor(
    /**
     * Dynamic optional dependency, that is present/absent in DI-graph based on declared runtime condition.
     */
    val myOptionalDependency: Optional<MyClassUnderRuntimeCondition>,
)

This is one of the approaches that can be taken into coding optional dependencies. Naturally, such thing can be written with Dagger's @Provides Optional<MyClassUnderRuntimeCondition> provide(..) { .. } yet such code is arguably difficult to maintain, verbose, and scales poorly with the growing number of conditions and classes under them.

Yatagan solves this by introducing first-class runtime condition support with compile-time condition validation. See Conditions/Variants APIs.

Usage (Gradle)

Code generation dependency is only required for project modules, that contain root component declarations (@Component(isRoot = true/* default */)). For modules, that contain classes with @Inject, @Provides, etc.. no dependencies but "api" ones are required. This is different for Dagger, which requires you to apply annotation processing in every module with DI code to allow Gradle incremental compilation/annotation processing to work correctly.

Yatagan can be used in various configurations. Choose one, that suits your project. See the following Gradle buildscript usage snippets (code is assumed to be inside a dependencies {} block).

For kotlin-only/mixed project using kapt:

// Ensure `kotlin-kapt` plugin is applied
api("com.yandex.yatagan:api-compiled:${yataganVer}")
// kapt is slow but generally reliable for mixed projects.
kapt("com.yandex.yatagan:processor-jap:${yataganVer}")

For kotlin-only/mixed project using KSP (use with caution for Java code): (How to apply KSP plugin)

// Ensure `com.google.devtools.ksp` plugin is applied
api("com.yandex.yatagan:api-compiled:${yataganVer}")
// KSP implementation is unstable. Works best for pure-Kotlin projects.
ksp("com.yandex.yatagan:processor-ksp:${yataganVer}")

To dramatically speed up build one can use runtime reflection instead of codegen:

// No codegen dependency is required, the reflection engine comes as a dependency of the `api-dynamic` artifact.
api("com.yandex.yatagan:api-dynamic:${yataganVer}")

For java-only project:

api("com.yandex.yatagan:api-compiled:${yataganVer}")
// best codegen backend for Java-only, no need to use kapt/ksp.
annotationProcessor("com.yandex.yatagan:processor-jap:${yataganVer}")

Android projects are advised to follow the same usage guidelines, though make sure to read the notes on reflection on Android. An example of a recommended way to use Yatagan for Android projects:

// Use reflection in debug builds.
debugApi("com.yandex.yatagan:api-dynamic:${yataganVer}")

// Use codegen in releases
releaseApi("com.yandex.yatagan:api-compiled:${yataganVer}")
if (kspEnabled) {
    kspRelease("com.yandex.yatagan:processor-ksp:${yataganVer}")
} else {
    kaptRelease("com.yandex.yatagan:processor-jap:${yataganVer}")
}

One may want to create a shared library that exposes a piece of Yatagan graph, yet doesn't create any root components itself. In this case, the library can depend on com.yandex.yatagan:api-public, which provides pure Yatagan API and no backend-specific entry-points.

Backends

KAPT/APT

APT or KAPT (Yatagan qualifies the artifacts with jap, java annotation processing) is a legacy backend, though it's stable and can be reliably used by default.

KSP support

Yatagan supports KSP in experimental mode. This is mostly due to the fact that Yatagan operates in terms of Java type system and is very sensitive to type equality. In Kotlin, Collection and MutableCollection are different types, though in Java it's the same type. From the other hand, Kotlin's Int is represented in Java as int and Integer. Choosing Java types to maintain semantic compatibility with Dagger, Yatagan converts Kotlin types into Java ones. KSP API related to JVM is explicitly marked as @KspExperimental, and practice shows KSP support for modeling Java code is at least inconsistent.

Thus, KSP can be adopted for Kotlin-only projects, or projects whose DI-code is mostly Kotlin. Additional care should be taken with Java projects.

Also, KSP strictly depends on Kotlin compiler version, used in your project, so using KSP may force you to keep updating Kotlin compiler version frequently.

Reflection support

Reflection support is considered stable in Yatagan. There's already a very similar project for the vanilla Dagger - dagger-reflect. However, Reflection mode in Yatagan has fist-class support and guaranteed to behave the same way, as generated implementation would. If a new feature is implemented in Yatagan, reflection automatically works with it.

Technically, reflection mode can be used in production, though it's advised not to do so, as code generation naturally produces much more performant code. Also, reflection mode is broken by code minifiers, such as Proguard or R8.

Read more in reflection backend specific notes.

Android

Reflection backend fully supports Android applications starting with minSdk = 24. Below that, static methods in interfaces are not directly supported in Android and have to be "desugared" by AGP. Yatagan Reflection doesn't currently read such desugared methods as they have no stable ABI and reading them will bring performance penalties. So consider using minSdk = 24 at least for debug build type to safely use Yatagan with Reflection.

Yatagan vs Dagger API reference

Dagger2 API (dagger.**) Status in Yatagan Notes
@Component 🟢 as is
@Component.Builder 🟢 as is supports factory method as well
@Component.Factory 🟡 converged functionality merged into @Builder
@Subcomponent 🟡 converged replaced by Component(isRoot = false)
@Subcomponent.{Builder/Factory} 🟡 converged replaced by Component.Builder
Lazy 🟢 as is now also extends javax.inject.Provider
@Module 🟢 as is
@Binds 🟡 tweaked can bind zero/multiple alternatives
@BindsInstance 🟢 as is
@Provides 🟢 as is supports conditional provision
@BindsOptionalOf 🟡 replaced replaced with Variants API
@Reusable 🟢 as is
MembersInjector 🔴 unsupported
@MapKey 🟡 renamed* IntoMap.Key, *unwrap=false is unsupported
@multibindings.IntoSet 🟢 as is
@multibindings.ElementsIntoSet 🟡 converged IntoSet(flatten = true)
@multibindings.Multibinds 🟢 as is
@multibindings.IntoMap 🟢 as is
@multibindings.{Int,Class,String}Key 🟢 as is
@multibindings.LongKey 🔴 removed can be declared manually if required
assisted.* 🟢 as is
producers.* 🔴 unsupported
android.* 🔴 unsupported
grpc.* 🔴 unsupported
hilt.** 🔴 unsupported
spi.* 🟡 replaced Yatagan has its own model for SPI

Other behavioral changes:

  • @Binds can't be scoped (scope rebind is not allowed). Use scope on the implementation. Also, Yatagan supports declaring multiple scopes on bindings, so the binding is compatible with every scope declared. Dagger only allowed doing so for components.

  • Yatagan requires components, builders, assisted inject factories to be declared as interfaces. Abstract classes are forbidden. This is due to the limitations of RT mode. Dagger-reflect has the same limitation.

  • If codegen is used, generated component implementations are not named Dagger<component-name>, their names are mangled, and the access should be made via Yatagan.builder()/Yatagan.create() entry-point invocations. This is made to support reflection backend. Actual Yatagan implementations are provided within com.yandex.yatagan:api-dynamic and com.yandex.yatagan:api-compiled artifacts.

  • Yatagan does not support @Nullable provisions. If a binding returns null, or a @BindsInstance is supplied with null, an error will be thrown at run-time. Currently, no compile-time validation is done in the matter.

  • Automatic component factory/builder generation is not supported - an explicit one must be written if required.

  • Member inject in Kotlin code should be used with care: @Inject lateinit var prop: SomeClass will work as expected, though @Inject @Named("id") lateinit var prop: SomeClass will not - qualifier annotation will go to the property instead of field, and Yatagan will not be able to see it. In fact vanilla Dagger will also fail to see it in some scenarios, though it tries to do so on the best-effort basis. Yatagan can't read annotations from Kotlin properties, so the following explicit forms should be used instead: @Inject @field:Named("id") lateinit var prop: SomeClass to inject directly to the field, or @set:Inject @set:Named("id") lateinit var prop: SomeClass to inject via setter.

Yatagan was written from scratch, and as major known inconsistencies are documented here, there is a possibility for differences that are overlooked. If you happen to discover one, please report it.

Migration from Dagger

Strictly speaking, Yatagan and Dagger are not directly compatible. Yatagan uses a separate binary-incompatible set of annotations and helpers to give it a degree of freedom to extend and enhance the API.

Yet for the majority of cases, as documented in the api reference, annotations and classes differ only in package names, which makes migration from Dagger to Yatagan somewhat trivial.

The general idea of steps one needs to take to migrate from Yatagan to Dagger:

  1. Replace import dagger\.multibindings\. -> import com.yandex.yatagan.
  2. Replace import dagger\.assisted\. -> import com.yandex.yatagan.
  3. Replace import dagger\. -> import com.yandex.yatagan.
  4. Replace @Subcomponent annotations with @Component(isRoot = false) ones.
  5. Replace @Component.Factory with @Component.Builder.
  6. Get rid of all nullable provisions. Yatagan does not support them.
  7. Replace DaggerMyComponent.builder() with Yatagan.builder(MyComponent.Builder::class.java) or similar.
  8. Mark all components, that are accessed from multiple threads as @Component(.., multiThreadAccess = true). If you are unsure, if a component is accessed from a single thread, but ideally it should be, you can set up a check with Yatagan.setThreadAsserter().
  9. Run build and fix all remaining inconsistencies (like implicitly included subcomponents, etc..).

Added APIs

Yatagan introduces the following new APIs, that can be utilized to work with conditional bindings

The first one is @Condition. With this annotation, one can declare a runtime condition that can be evaluated and its value will determine the presence/absence of a binding under the condition.

To put a binding under a given condition, one must use @Conditional annotation on a binding or a class with @Inject-annotated constructor.

Variant API ideally replaces Dagger's @BindsOptionalOf and makes it more powerful. It's very alike to how Android works with flavors and dimensions, only here we can declare components having such flavors and include/exclude bindings based on them. To use that, one can employ @Conditional(.., onlyIn = ...) and @Component(variant = ...) attributes.

Feel free to read a small tutorial doc, that includes how to use conditions and variants.

Plugins

One can write an extension for validation pipeline for Yatagan to implement one's custom graph inspections. No additional code generation is currently supported for plugins, and they can not modify graphs under inspection. This works, as for Dagger, via SPI. Read more here.

Options

Yatagan has some options, that tweak its behavior. They are provided as normal annotation processor options. However, reflection backend requires a different approach in specifying them, as documented here.

Option key Default value Description
yatagan.enableStrictMode true if enabled, every mandatory warning is reported as an error
yatagan.maxIssueEncounterPaths 5 the max number of places Encountered in in an error message to be mentioned
yatagan.usePlainOutput false if enabled, reporting is done in plain text, without ANSI coloring

More Repositories

1

gixy

Nginx configuration static analyzer
Python
8,242
star
2

YaLM-100B

Pretrained language model with 100B parameters
Python
3,732
star
3

odyssey

Scalable PostgreSQL connection pooler
C
3,190
star
4

yandex-tank

Load and performance benchmark tool
Python
2,439
star
5

YaFSDP

YaFSDP: Yet another Fully Sharded Data Parallel
Python
821
star
6

rep

Machine Learning toolbox for Humans
Jupyter Notebook
687
star
7

pgmigrate

Simple tool to evolve PostgreSQL schema easily.
Python
623
star
8

faster-rnnlm

Faster Recurrent Neural Network Language Modeling Toolkit with Noise Contrastive Estimation and Hierarchical Softmax
C++
562
star
9

tomita-parser

C
495
star
10

pandora

A load generator in Go language
Go
401
star
11

porto

Yet another Linux container management system
C++
397
star
12

reshadow

Markup and styles that feel right
JavaScript
363
star
13

django_replicated

Django DB router for stateful master-slave replication
Python
351
star
14

pire

Perl Incompatible Regular Expressions library
C++
330
star
15

metrica-tag

The client library of the web analytics tool. It is in the top 5 by popularity worldwide.
TypeScript
257
star
16

ozo

OZO is a C++17 Boost.Asio based header-only library for asyncronous communication with PostgreSQL DBMS.
C++
226
star
17

mapsapi-codestyle

JavaScript and TypeScript Style Guide
JavaScript
213
star
18

zero-downtime-migrations

Apply Django migrations on PostgreSql without long locks on tables
Python
188
star
19

audio-js

Библиотека аудио-плеера для браузера
JavaScript
186
star
20

alice-skills

Примеры кода навыков для голосового помощника, придуманного в Яндексе
Python
179
star
21

burp-molly-scanner

Turn your Burp suite into headless active web application vulnerability scanner
Java
154
star
22

yandex-taxi-testsuite

testsuite: microservices testing framework
Python
144
star
23

burp-molly-pack

Security checks pack for Burp Suite
Java
137
star
24

yatool

Yatool is a cross-platform distribution, building, testing, and debugging toolkit focused on monorepositories
C++
136
star
25

go-hasql

Go library for accessing multi-host SQL database installations
Go
133
star
26

mapsapi-modules

Async modular system
JavaScript
132
star
27

mapkit-android-demo

MapKit Android demo
Kotlin
121
star
28

yoctodb

A tiny embedded Java-engine for extremely fast partitioned immutable-after-construction databases
Java
106
star
29

scout

A fast and safe manual dependency injector for Kotlin and Android.
Kotlin
104
star
30

handystats

C++ library for collecting user-defined in-process runtime statistics with low overhead
C++
97
star
31

speechkitcloud

Speechkit Cloud examples and SDK
JavaScript
89
star
32

fastops

This small library enables acceleration of bulk calls of certain math functions on AVX and AVX2 hardware. Currently supported operations are exp, log, sigmoid and tanh. The library is designed with extensibility in mind.
C++
88
star
33

mapkit-ios-demo

MapKit iOS demo
Swift
82
star
34

argon2

Implementation of argon2 (i, d, id) algorithms with CPU dispatching
C++
80
star
35

mapsapi-heatmap

Heatmap: Yandex.Maps API plugin for data visualization
JavaScript
77
star
36

mms

Memory-mapped storage library
C++
76
star
37

geo-reviews-dataset-2023

76
star
38

tcplanz

TCPDump latency analyzer
Python
74
star
39

balancer

http balancer
C
72
star
40

securitygym

Python
72
star
41

NwSMTP

Asynchronous SMTP proxy server
Shell
72
star
42

smart

SMT-aware Real-time scheduler for Linux
C
67
star
43

mysync

MySync is mysql high-availability and cluster configuration tool.
Go
64
star
44

YandexDriver

YandexDriver is a WebDriver implementation
63
star
45

rtv

Remote TV control for developers
JavaScript
62
star
46

tex-renderer

Микросервис для рендеринга tex-формул в изображения
JavaScript
61
star
47

yandex_tracker_client

Python client for working with Yandex.Tracker Api
Python
55
star
48

csp-tester

This extension helps web masters to test web application behaviour with Content Security Policy (CSP) ver. 1.0 implemented.
JavaScript
55
star
49

mapsapi-examples

Примеры использования API Яндекс.Карт
JavaScript
52
star
50

ofd

Реализация протокола взаимодействия ККТ-ОФД
Python
49
star
51

csp-reporter

Content Security Policy logs parser
Python
44
star
52

reselector

Use React Components in css selectors
JavaScript
44
star
53

CMICOT

Efficient feature selection method based on Conditional Mutual Information.
C++
42
star
54

dpt

BEM-based prototyping framework for large projects
JavaScript
41
star
55

pgcheck

Tool for monitoring backend databases from PL/Proxy hosts and changing plproxy.get_cluster_partitions() output
Go
37
star
56

root-2015-tasks

Yandex.Root 2015 contest data
Python
34
star
57

deaf

Android App for Deaf
Java
33
star
58

datasync-js

DataSync API allows for structured data storage and synchronization in Web services and mobile applications.
JavaScript
33
star
59

inet64_tcp

Magic thing to make old Erlang stuff work in IPv6-only networks
Erlang
32
star
60

browser-extensions

JavaScript
32
star
61

mongoz

An alternative implementation of MongoDB sharding server aimed at high availability
C++
32
star
62

mapsapi-pie-chart-clusterer

Yandex Maps Plugin: Pie Chart Clusterer
JavaScript
31
star
63

mlcup

Official baseline solutions to Yandex Cup ML challenge
Jupyter Notebook
30
star
64

webmaster.api

28
star
65

mapsapi-polylabeler

Plugin to setting labels inside polygons
JavaScript
25
star
66

ch-backup

Backup tool for ClickHouse DBMS
Python
25
star
67

mapsapi-round-controls

Plugin for Yandex.Maps JS API: rounded map controls theme
JavaScript
24
star
68

ch-tools

ClickHouse administration and diagnostics tools
Python
23
star
69

pgconsul

PgConsul is a tool for maintaining High-Availability Postgresql cluster configurations. It is responsible for cluster recovery in case of emergencies.
Python
22
star
70

rdsync

Go
22
star
71

dep_tregex

Stanford Tregex-inspired language for rule-based dependency tree manipulation.
Python
21
star
72

tartifacts

📦 Create artifacts for your assemblies
JavaScript
20
star
73

mastermind

Smart control for a big storage
Python
19
star
74

cggen

Tool for generating Core Graphics code from vector image files
Swift
19
star
75

sdch_module

C++
18
star
76

YNDX000SB_kernel

Yandex.Phone kernel sources
C
18
star
77

openvpn-python-plugin

Runs python3 interpreter inside OpenVPN process in a persistent manner to answer it's plug-in calls.
C
18
star
78

yandex-ecom-search

Бета-Версия документации для разработчиков по работе с товарным фидом Яндекс Поиска
18
star
79

temporal-over-ydb

Go
17
star
80

evgen

Code generation for event logging
TypeScript
16
star
81

vgsl

Very Good Swift Library
Swift
15
star
82

miniapp-example

Example application for brand new platform of MiniApps inside the Yandex App
TypeScript
15
star
83

cluster_metrics

C++
13
star
84

yamail

YMail General Purpose Library
C++
13
star
85

agglomerative_clustering

C++
13
star
86

minishard

Lightweight sharding for distributed erlang applications
Erlang
12
star
87

jsx-directives

Директивы для JSX
TypeScript
12
star
88

mapsapi-ios

Allows to easily add Yandex.Maps to your existing iOS project using Yandex.Maps JavaScript API
Objective-C
11
star
89

miniapp-example-backend

Backend for Miniapp Example App for brand new platform of MiniApps inside the Yandex App
TypeScript
10
star
90

erater

Generic embedded distributed request rate limiting service for erlang applications
Erlang
10
star
91

storytests-cli

Framework agnostic CLI Utility to generate test files from Storybook
TypeScript
10
star
92

mapsapi-area

util.calculateArea: plugin for calculating geodesic features area.
JavaScript
10
star
93

zest

Библиотека для взаимодействия с бэкендом
TypeScript
9
star
94

opentsdb-flume

Module for flume, allows to write incoming events directly to OpenTSDB.
Java
9
star
95

protoc-gen-crd

Protobuf plugin for generating k8s CRD
Go
8
star
96

mediastorage-proxy

Mediastorage-proxy is a HTTP proxy for mediastorage based on elliptics
C++
8
star
97

toolchain-registry

Toolchain Registry is a central registry for maintaining and versioning toolchains used in Yandex.
Python
8
star
98

erateserver

Distributed rate limiting service with HTTP interface
Erlang
7
star
99

domestic-roots-patch

A patch that adds support for the Russian domesic root certificate to the Chromium browser.
7
star
100

libmastermind

Client library for mastermind
C++
6
star