Skip to content

Instantly share code, notes, and snippets.

@igor-vovk
Created June 25, 2023 06:15
Show Gist options
  • Save igor-vovk/538b904939b1f37c32aee18982043397 to your computer and use it in GitHub Desktop.
Save igor-vovk/538b904939b1f37c32aee18982043397 to your computer and use it in GitHub Desktop.
Dependency Injection with cats-effect Resource Monad
import cats.effect.*
import cats.effect.unsafe.IORuntime
import org.postgresql.core.ConnectionFactory
// repository, two services, and api:
class Repository(conn: ConnectionFactory) {}
class ServiceA(repo: Repository) {}
class ServiceB(repo: Repository) {}
class HttpServerTask(serviceA: ServiceA, serviceB: ServiceB) {
def run: IO[Unit] = ???
}
class Allocator(implicit runtime: IORuntime) {
// Ref that will keep track of finalizers
private val shutdown: Ref[IO, IO[Unit]] = Ref.unsafe(IO.unit)
// Method to allocate dependencies
def allocate[A](resource: Resource[IO, A]): A =
resource.allocated.flatMap { case (a, release) =>
// Shutdown this resource, and after shutdown all previous
shutdown.update(release *> _).map(_ => a)
}.unsafeRunSync()
// Shutdown dependencies
def shutdownAll: IO[Unit] = {
shutdown.getAndSet(IO.unit).flatten
}
}
// Dependency Injection part:
object Dependencies {
// Safe method to create dependencies:
def apply(runtime: IORuntime): Resource[IO, Dependencies] =
Resource.make {
IO(unsafeCreate(runtime))
} {
_.allocator.shutdownAll
}
// Unsafe method, use it carefully as no shutdown is executed:
def unsafeCreate(runtime: IORuntime): Dependencies =
new Dependencies(new Allocator()(runtime))
}
class Dependencies(val allocator: Allocator) {
lazy val conn: ConnectionFactory = allocator.allocate {
???
}
lazy val repo = new Repository(conn)
lazy val serviceA = new ServiceA(repo)
lazy val serviceB = new ServiceB(repo)
lazy val server = HttpServerTask(serviceA, serviceB)
}
// Runner class
object Main extends IOApp.Simple {
// Now we can use any dependency and not just `server` as all of them are exposed:
private val dependencies = Dependencies(IORuntime.global)
override def run = dependencies.use(_.server.run)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment