inject
The inject
library simplifies the scaffolding required to start and stop
applications, and in particular, command line tools.
Use
With the inject
library, the key pieces of an application which carry global
state -- think flags, parsed configuration -- are represented as individual
pieces dubbed modules.
Each module is responsible for its little piece of the world, and its scope
should be small. When creating a module, think about the principle of least
knowledge to scope it down to its
essence. For instance, in a code generation application, a "templates" module
could be responsible for providing the global *template.Template
which is used
to start the rendering.
Modules can depend on each other, and are allowed to form a directed acyclic
graph (DAG). Conceptually, a module will depend on another if it uses the
functionality of the child module in order to provide its functionality. In this
case, it is common for all dependencies on the child to go through the parent,
i.e. the parent provides an abstraction layer over the child. Continuing the
example above, a "generator" module which emits code to a io.Writer
provided
an intermediate representation, would depend on the "templates" module describe
above.
An application startup process starts at a single root module, often called the
"app" module, then instantiates and starts all dependent module in reverse
depth-first traversal order. For instance, with the very simple module graph:
┌───────────┐ ┌───────────┐ ┌───────────┐
│ app │───▷ generator │───▷ templates │
└───────────┘ └───────────┘ └───────────┘
The application startup would first start the "templates" module, then the
"generator" module, and lastly the "app" module.
Defining modules
Concretely, to define a module, create a struct type:
type app struct {
...
}
The module itself does not need to be exported, though package structure will
often push you to export it.
Each dependency that this module has on other modules is represented as a member
annotated with the `inject:""`
tag:
type app struct {
Generator *codegen.Generator `inject:""`
}
When a dependency is listed, this instructs the inject
library to ensure the
child modules are instantiated, and started before the parent module is itself
started. (And vice-versa with stopping, where the parent will be stopped before
the child is stopped.)
A module can request to be started by implementing the inject.Start
interface,
or request to be stopped by implementing the inject.Stop
interface.
References
The inject
library is a dependency injection
framework. The most widely
used framework is Guice. This library solves
a much smaller problem, that of starting and stopping applications, not of
supporting dynamic injection at runtime. Such dynamic injection brings about
much complexity: concurrency model, lifetimes, scoping. All of that is
purposefully out of scope.
This library is heavily influenced by Facebook's
inject and
startstop.
Develop
Configure
fx set core.x64 --with //src/lib/inject:tests
And run the tests
fx test inject_test