dep-t-dynamic
This library is a compation to dep-t and in particular it complements the Dep.Env
module. It provides various types of dependency injection environments that are dynamically typed to some degree: missing dependencies are not detected at compilation time. Static checks are sacrificed for advantages like faster compilation.
- Dep.Dynamic is the simplest dynamic environment, but it doesn't give many guarantees.
- Dep.Checked and Dep.SimpleChecked give greater guarantees at the cost of more ceremony and explicitness. Dep.Checked can only be used with the
DepT
monad.
Rationale
In dep-t, functions list their dependences on "injectable" components by means of Has
constraints. One step when creating your application is defining a big environment record whose fields are the components, and giving it suitable Has
instances.
Environments often have two type parameters:
- One is an
Applicative
"phase" that wraps each field and describes how far along we are in the process of constructing the environment (the Identity
function correspond to a "finished" environment, ready to be used).
- The other is the effect monad which parameterizes each component in the environment.
Usually environments will be vanilla Haskell records. It has the advantage that "missing" dependencies are caught at compile-time. But using records might be undesirable is some cases:
- For environments with a big number of components, compiling the environment type might be slow.
- Defining the required
Has
instances for the environment might be a chore, even with the helpers from Dep.Env
.
How Dep.Dynamic
helps
DynamicEnv
from Dep.Dynamic
allows registering any component at runtime.
Because there aren't static fields to check, compilation is faster.
DynamicEnv
also has a Has
instance for any component! However, if the component hasn't been previously registered, dep
will throw an exception.
Isn't that a wee bit too unsafe?
Yeah, pretty much. It means that you can forget to add some seldomly-used
dependency and then have an exception pop up at an inconvenient time when that
dependency is first exercised.
That's where Dep.Checked
and Dep.SimpleChecked
may help.
How can the -Checked
modules help?
They define wrappers around DynamicEnv
that require you to list each component's dependencies as you add them to the environment. Then, before putting the environment to use, they let you check at runtime that the dependencies for all components are present in the environment.
It's more verbose and explicit, but safer. It makes easy to check in a unit test that the environment has been set up correctly.
As a side benefit, the -Checked
modules give you the graph of dependencies as a value that you can analyze and export as a DOT file.
Dep.Checked
can only be used when your dependencies live in the DepT
monad. Use Dep.SimpleChecked
otherwise.
Relationship with the "registry" package
This library is heavily inspired in the registry package, which provides a Typeable
-based method for performing dependency injection.
This library is more restrictive and less ergonomic in some aspects, but it fits better with how dep-t works.
Some notable differences with registry:
Dep.Dynamic
only reports missing dependencies when the program logic first searches for them, while registry reports them when calling withRegistry
.
Dep.Checked
and Dep.SimpleChecked
do allow you to find missing dependencies before running the program logic, but they force you to explicitly list the dependencies of each new component you add to the environment, something that registry doesn't require.
- Unlike in registry, there are no specific warmup combinators. Allocating the resources required by a component must be done in an
Applicative
"phase" of a Phased
environment.