Skip to content

danidiaz/dep-t-dynamic

Repository files navigation

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-t-dynamic.png

  • 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.