Git Product home page Git Product logo

cats-effect-simple-di's Introduction

Simple Dependency Injection for Cats-Effect

A tiny library that makes dependency injection with cats-effect simple. This is a follow-up of the article I wrote about the topic.

Traditional approach to dependency injection with cats-effect is to build a single for-comprehension that wires all the dependencies together. This approach is not very scalable and can become quite messy as the number of dependencies grows.

The suggested approach with this library would be:

import io.github.cats_effect_simple_di.Allocator

// create a Dependencies object and class that holds all the dependencies:
object Dependencies {
  def create(runtime: IORuntime): Resource[IO, Dependencies] =
    Allocator.create(runtime).map(new Dependencies(_))
}

class Dependencies private(allocator: Allocator) {
  // Suppose you need to instantiate a class that returns a Resource[F, A]
  // Then you can use the allocator to allocate the resource
  lazy val http4sClient: Client[IO] = allocator.allocate {
    EmberClientBuilder.default[IO].build
  }

  // Dependencies that don't need to be shut down can be used directly
  lazy val myClass: MyClass = new MyClass(http4sClient)

  // Dependencies will be shut down in the right order
  lazy val myServer: Server[IO] = allocator.allocate {
    BlazeServerBuilder[IO](runtime.compute)
      .bindHttp(8080, "localhost")
      .withHttpApp(myClass.httpApp)
      .resource
  }

}

// Use your dependencies in the main app class
object Main extends IOApp.Simple {
  override def run: IO[Unit] =
    Dependencies.create(runtime).use { dependencies =>
      // use your exit dependency here
      dependencies.myServer.useForever
    }
}

What happens under the hood?

  • lazy val solves the problem that dependencies are instantiated only when they are accessed and only one instance is created.
  • Allocator is a wrapper around Resource that keeps track of the order of resource allocation and finalization. So when application is shut down, resources are shut down in the reverse order they were initialized.
  • Dependencies initialization is wrapped in a Resource so that resources are shut down when the application finishes.

Installation

Supported Scala versions: 3.x

To install add the following to your build.sbt:

libraryDependencies ++= Seq(
  "org.typelevel" %% "cats-effect" % Versions.catsEffect,
  "io.github.igor-vovk" %% "cats-effect-simple-di" % Versions.simpleDi,
)

Debugging allocation order

If you want to see the order of initialization and finalization of resources, use LogbackAllocationListener when creating an Allocator object. This will log the allocation and finalization of resources in the order they happen:

import io.github.cats_effect_simple_di.AllocationLifecycleListener

Allocator.create(runtime, LogbackAllocationListener)

Modularization

You can have multiple dependencies objects and combine them together. In this case, you can either reuse the same Allocator object or create a new one for each dependency object, but wrap their instantiation in allocator.allocate{} so that they are shut down in the right order:

Example reusing the same Allocator object:

// AWS - specific dependencies
class AwsDependencies(allocator: Allocator) {
  lazy val s3Client: S3Client = allocator.allocate {
    S3ClientBuilder.default.build
  }
}

// Main application dependencies
object Dependencies {
  def create(runtime: IORuntime): Resource[IO, Dependencies] =
    Allocator.create(runtime).map(new Dependencies(_))
}

class Dependencies(allocator: Allocator) {
  val aws = new AwsDependencies(allocator)

  lazy val http4sClient: Client[IO] = allocator.allocate {
    EmberClientBuilder.default[IO].build
  }
}

object App extends IOApp.Simple {
  override def run: IO[Unit] = Dependencies.create(runtime).use { deps =>
    // use aws.s3Client here
    deps.aws.s3Client
  }
}

Example creating a new Allocator object for each Dependencies object:

// AWS - specific dependencies
object AwsDependencies {
  def create(runtime: IORuntime): Resource[IO, AwsDependencies] =
    Allocator.create(runtime).map(new AwsDependencies(_))
}

class AwsDependencies(allocator: Allocator) {
  lazy val s3Client: S3Client = allocator.allocate {
    S3ClientBuilder.default.build
  }
}

// Main application dependencies
object Dependencies {
  def create(runtime: IORuntime): Resource[IO, Dependencies] =
    Allocator.create(runtime).map(new Dependencies(_))
}

class Dependencies(allocator: Allocator) {
  lazy val aws = allocator.allocate {
    AwsDependencies.create(IORuntime.global)
  }

  lazy val http4sClient: Client[IO] = http4sAllocator.allocate {
    EmberClientBuilder.default[IO].build
  }
}

object App extends IOApp.Simple {
  override def run: IO[Unit] = Dependencies.create(runtime).use { deps =>
    // use aws.s3Client here
    deps.aws.s3Client
  }
}

cats-effect-simple-di's People

Contributors

igor-vovk avatar scala-steward avatar

Stargazers

Maksym Besida avatar

Watchers

Marco Zühlke avatar  avatar

Forkers

scala-steward

cats-effect-simple-di's Issues

Access to GitHub Package Registry no possible

Thanks for transferring the code from your article into a library.

I added to my build.sbt the resolver as shown in the Readme:

ThisBuild / resolvers += "GitHub Package Registry (igor-vovk)" at "https://maven.pkg.github.com/igor-vovk/_"

But when trying to downloading from that package repository sbt throws an error:

[warn] 	Note: Unresolved dependencies path:
[error] stack trace is suppressed; run 'last app / update' for the full output
[error] (app / update) sbt.librarymanagement.ResolveException: Error downloading io.github.igor-vovk:cats-effect-simple-di_3:0.1.1
[error]   Not found
[error]   Not found
[error]   not found: https://repo1.maven.org/maven2/io/github/igor-vovk/cats-effect-simple-di_3/0.1.1/cats-effect-simple-di_3-0.1.1.pom
[error]   unauthorized: https://maven.pkg.github.com/igor-vovk/_/io/github/igor-vovk/cats-effect-simple-di_3/0.1.1/cats-effect-simple-di_3-0.1.1.pom (GitHub Package Registry)

Are the Github package repos not general accessible ?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.