yakivy / jam   0.1.0

GitHub

Incredibly simple DI Scala library.

Jam Jam

Maven Central Sonatype Nexus (Snapshots) Build Status License: MIT

Jam is an incredibly simple DI Scala library.

Essential differences from macwire:

  • is simpler, faster and more predictable
  • supports Scala 3, Scala JS, Scala Native
  • is able to inject arguments recursively
  • searches candidates in this

Quick start

Latest stable jam dependency:

"com.github.yakivy" %% "jam-core" % "0.1.0"

Usage example:

class DatabaseAccess()
class SecurityFilter(databaseAccess: DatabaseAccess)
class UserFinder(databaseAccess: DatabaseAccess, securityFilter: SecurityFilter)
class UserStatusReader(userFinder: UserFinder)

trait UserModule {
    val singletonDatabaseAccess = jam.brewRec[DatabaseAccess]
    val userStatusReader = jam.brewRec[UserStatusReader]
}

Macro output:

trait UserModule {
    val singletonDatabaseAccess = new DatabaseAccess()
    val userStatusReader = new UserStatusReader(
        new UserFinder(
            singletonDatabaseAccess,
            new SecurityFilter(singletonDatabaseAccess)
        )
    )
}

Brew types

  • jam.brew - injects constructor arguments if they are provided in this, otherwise throws an error
  • jam.brewRec - injects constructor arguments if they are provided in this or recursively brews them
  • jam.brewWith - injects lambda arguments if they are provided in this, otherwise throws an error, especially useful when constructor cannot be resolved automatically:
class PasswordValidator(databaseAccess: DatabaseAccess, salt: String)
object PasswordValidator {
    def create(databaseAccess: DatabaseAccess): PasswordValidator =
        new PasswordValidator(atabaseAccess, "salt")
}

trait PasswordValidatorModule extends UserModule {
    val passwordValidator = jam.brewWith(PasswordValidator.create _)
}
  • jam.brewFrom - injects constructor arguments if they are provided in self argument, otherwise throws an error:
class QuotaChecker(databaseAccess: DatabaseAccess)

trait QuotaCheckerModule {
    object ResolvedUserModule extends UserModule

    val quotaChecker = jam.brewFrom[QuotaChecker](ResolvedUserModule)
}

Implementation details

  • injection candidates is being searched in this instance, so to provide an instance for future injection you need to make it a member of this. Examples:
trait A {
    val a = new A
    ...brewing //val a will be used
}

val container = new {
    val a = new A
    ...brewing //val a will be used
}

trait A {
    def b(): String = {
        val a = new A
        ...brewing //val a will be ignored
    }
}

trait A {
    val a1 = new A
    {
        val a2 = new A
        ...brewing //val a1 will be used
    }
}
  • val member works like singleton provider (instance will be reused for all injections in this score), def member works like prototype provider (one method call per each injection)
  • library injects only non implicit constructor arguments, implicits will be resolved by compiler
  • jam is intended to be minimal, features like scopes or object lifecycles should be implemented manually

Changelog

0.1.0:

  • fix import ambiguity: jam.tree.brew was renamed to jam.brewRec
  • fix method overload ambiguity: jam.brew(f) was renamed to jam.brewWith(f)
  • allow passing an instance to brew from (instead of this): jam.brewFrom
  • various refactorings and cleanups