Welcome to OOverkommelig
Dependency injection that is easy to read and write. Written in and for Kotlin.
Current status: beta. API is stable. Documentation is needed.
To use OOverkommelig in your projects, add the following to your Gradle scripts:
repositories {
mavenCentral()
}
dependencies {
// For JavaScript (which does not support all features):
implementation 'org.ooverkommelig:ooverkommelig-js:1beta4'
// For the JVM:
implementation 'org.ooverkommelig:ooverkommelig-jvm:1beta4'
}
And if you use Kotlin for your Gradle scripts:
repositories {
mavenCentral()
}
dependencies {
// For JavaScript (which does not support all features):
implementation("org.ooverkommelig:ooverkommelig-js:1beta4")
// For the JVM:
implementation("org.ooverkommelig:ooverkommelig-jvm:1beta4")
}
Get notified of new releases:
Example
Here is some (very simplistic) example code to give you a taste. For more complex object graphs see the examples:
// An object graph definition is a self-contained unit consisting of multiple
// object definitions, i.e. it can be instantiated.
//
// A single interface parameter with one or more functions provides external
// objects (or object definitions), allowing configuration of the object
// graph.
class ApplicationOgd(provided: Provided) : ObjectGraphDefinition() {
// Explicit declaration of the dependencies of an object graph, so clients
// of the graph quickly know what they need to provide to be able to use
// the graph.
//
// Functions instead of values are used, to reduce the boilerplate in
// certain situations. For example, when using a definition of one sub
// graph for the dependency of another sub graph of the same parent graph.
// See `org.ooverkommelig.examples.dagger.simple.CoffeeAppOgd` in the
// examples.
//
// "Definition" can occur quite a number of times in (sub) graph
// definitions. Its type alias "D" can be used to reduce the amount of
// boilerplate.
interface Provided {
fun applicationLogDirectory(): Definition<File>
}
// This is "public", which is not a requirement but reduces the amount of
// boilerplate.
//
// In this case all dependencies have to be provided by the client of
// "ApplicationOgd". But it is also possible that "ApplicationOgd"
// provides dependencies by using multiple sub graph definitions, and
// using an object definition from one for the dependency of another.
val appSgd = add(ApplicationSgd(object : ApplicationSgd.Provided {
override fun logDirectory() = provided.applicationLogDirectory()
}))
// By creating an instance of this class and requesting one or more
// objects, the defined object will actually be created. Note that
// OOverkommelig also allows for specifying eagerly created objects that
// will be created once a graph instance is created.
//
// The graph instance implements "Closeable", which enables clean-up of
// objects in the graph. When you only need a graph for a single operation
// you can use the following code. This ensures that all objects are
// disposed of once the operation finished:
// ApplicationOgd(object : ApplicationOgd.Provided { ... })
// .Graph().use { graph ->
// graph.someService().doYourThing()
// }
//
// In cases where you need the graph for multiple operations, you create
// and store it. When it is no longer needed, you dispose of it.
inner class Graph : DefinitionObjectGraph() {
fun someService() = req(appSgd.someService)
}
}
// A sub graph definition is not self-contained, i.e. it is intended to be
// used in one or more object graphs. The object graph must provide the
// dependencies for the sub graphs it uses, either by using object definitions
// of other sub graphs or by requiring the object graph client to provide
// them.
//
// Sub graph definitions are the reusable pieces of objects and wiring that
// can be used in multiple contexts. For example:
// - The object definitions that are the same for both Android and iOS apps.
// - Infrastructure objects that are the same for all applications that you
// build.
class ApplicationSgd(provided: Provided) : SubGraphDefinition() {
interface Provided {
fun logDirectory(): Definition<File>
}
// This is a very simple object definition as it only specifies the
// creation lambda. OOverkommelig also support wiring (allowing object
// cycles to be created), initialization and disposal.
//
// The creation lambda of this object will be invoked only once, and its
// result will be returned to any other object needing the log file.
// OOverkommelig supports a range of object definitions: constant, once,
// always, parameterized once, parameterized always, aspect once (JVM
// only) and aspect always (JVM only).
//
// Again: The values are public to reduce the amount of boilerplate, but
// this is not a requirement.
//
// "req(...)" indicates that the dependency is required. OOverkommelig
// also supports optional dependencies using "opt(...)".
//
// The object definitions allow enormous flexibility: You can use all
// language constructs and all APIs to determine what should be
// constructed, how it should be configured, etc. You can do much more
// than shown in this simplistic example.
val logFile by Once { File(req(provided.logDirectory()), "log.txt") }
val log by Once { Log(req(logFile)) }
val someService by Once { SomeService(req(log)) }
}
The example above and other examples can be found in the repository.
Advantages
Some of the big advantages of OOverkommelig compared to other dependency injection containers are:
-
The ability to quickly navigate to the definition of a dependency.
-
The ability to quickly navigate from a dependency to its usages.
-
Compile-time check for graph completeness, i.e. compile errors if you reference a dependency that is not defined.
-
Compile-time check of type correctness, i.e. you cannot use an object definition for another type for a dependency.
-
Compile-time check of required dependencies, i.e. you cannot use an optional object definition for a required dependency.
-
Compile-time check of unused dependencies, i.e. your IDE will mark object definitions that are not used by other object definitions.