DI for Cats-Effect Resource
kindlings-di-cats adds one macro on top of the DI module: DICats.wireResource, which wires a value whose
dependencies are Cats-Effect Resources (or effects, or plain values) and hands
you back a single composed Resource.
It is the Kindlings answer to macwire's autowire, with one important difference: it is not tied to any particular
effect type. macwire's autowire always returns Resource[IO, _]; wireResource is parametric in F[_], so it
works for IO, SyncIO, or any other effect — and needs no Sync/Async/MonadCancel constraint, because
Resource's pure/eval/flatMap require none.
Installation
kindlings-di-cats is published for the JVM and Scala.js. (Scala Native support follows the Cats-Effect Native
artifact, first published from cats-effect 3.7.0.)
Quick start
Give wireResource the dependencies it should compose; it resolves each parameter of the target's constructor from
them and threads the Resource plumbing for you:
//> using dep com.kubuszok::kindlings-di-cats:0.3.1
//> using dep org.typelevel::cats-effect:3.6.3
import hearth.kindlings.dicats.DICats
import cats.effect.{Resource, SyncIO}
class Db
class Service(db: Db)
val dbResource: Resource[SyncIO, Db] = Resource.eval(SyncIO(new Db))
// db is taken from the Resource and threaded through; the result is a single Resource[SyncIO, Service]:
val app: Resource[SyncIO, Service] = DICats.wireResource[SyncIO, Service](dbResource)
println(app.use(service => SyncIO(service.getClass.getSimpleName)).unsafeRunSync())
// expected output:
// Service
What can be a dependency
Each argument to wireResource is classified by its type:
| Dependency type | How it is used |
|---|---|
Resource[F, X] |
acquired with flatMap; its X is available to the construction |
F[X] (an effect) |
lifted with Resource.eval, then flatMaped |
plain X |
used directly — spliced into the construction with no Resource wrapping |
The target itself is built from its public primary constructor (or, failing that, a single matching companion
apply, or a companion resource factory — see below). Acquisition happens in the order the dependencies are given and
release happens in reverse, exactly as if you had written the nested flatMaps by hand.
import cats.effect.{Resource, SyncIO}
class Config
class Db
class Cache
class App(config: Config, db: Db, cache: Cache)
val config = new Config // plain value
val db: Resource[SyncIO, Db] = Resource.eval(SyncIO(new Db))
val cache: SyncIO[Cache] = SyncIO(new Cache) // effect
val app: Resource[SyncIO, App] = DICats.wireResource[SyncIO, App](config, db, cache)
Companion resource[F] factories
A very common pattern is for a component to expose its own lifecycle via a companion method
def resource[F[_]: <constraints>]: Resource[F, T]. wireResource discovers and uses it automatically — when it
needs a T that no dependency provides, and T's companion has a resource method, it applies your F and lets the
compiler resolve the context-bound implicits (e.g. Sync[F]). You don't have to pass such a component in yourself:
import cats.effect.{Resource, Sync, SyncIO}
class Connection
object Connection {
// acquisition/release is defined here; `F` is chosen by whoever calls `wireResource`
def resource[F[_]: Sync]: Resource[F, Connection] =
Resource.make(Sync[F].delay(new Connection))(_ => Sync[F].unit)
}
class Service(connection: Connection)
// Connection is built via `Connection.resource[SyncIO]` — no need to provide it explicitly
val service: Resource[SyncIO, Service] = DICats.wireResource[SyncIO, Service]()
This is a Kindlings extension over macwire's autocats (it has no equivalent). Both constraint-free
(def resource[F[_]]) and context-bounded (def resource[F[_]: Sync]) shapes are supported; a resource method that
takes explicit value parameters is left to ordinary construction.
Debugging
di-cats has the same debugging capabilities as kindlings-di:
- Generation logging — import
hearth.kindlings.dicats.debug._(or set-Xmacro-settings:diCats.logGeneration=true) to print the resolution logic, the wiring graph, and the generatedResourcecode. - Wiring graph — set
-Xmacro-settings:diCats.logWiring=tree(or=mermaid) to see only how the resources combine, as a ZIO-Magic-style dependency tree (or Mermaid diagram). Provided instances,Resource/effect dependencies, factories, andresource[F]factories are each labelled in the graph.
Errors
wireResource mirrors macwire's diagnostics. If no dependency provides a constructor parameter you get a
"Cannot find a value of type …" compile error; if two dependencies could provide the same parameter you get an
"Ambiguous …" error.
Relationship to kindlings-di
Use kindlings-di (wire, wireSet, …) when you wire plain values from the enclosing lexical scope. Reach
for kindlings-di-cats when the components are lifecycle-managed Resources that must be acquired and released
together.