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.