Managerial: like Twitter Util's `Managed`, without the Twitter Util

If you:

  • like the style of Twitter Util Managed (resource composition via monads),
  • don’t want the Twitter Util dependencies (or any dependencies at all),
  • do want a nicer API,

you may enjoy GitHub - dvgica/managerial: A zero-dependency Scala library for managing resources monadically, published for Scala 2.12, 2.13, and 3.0. The lib is likely closest to Scala ARM, but smaller in scope and published for recent versions of Scala.

Let me know what you think!

Full example:

import ca.dvgi.managerial._

object Main extends App {

  val server = for {
    // create a Managed[Unit] for side effects
    _ <- Managed.eval(println("Starting setup..."))(println("Finished teardown"))

    // create Managed[Settings] that doesn't require teardown
    settings <- Managed.setup(Settings(8080, 7070))

    // create Managed[HealthCheckServer], which requires teardown
    healthCheckServer <- Managed(new HealthCheckServer(settings))(_.stop())

    // Managed#from expects a type class instance for Teardown[T], instead of having teardown specified explicitly.
    // ca.dvgi.managerial provides Teardown[AutoCloseable].
    _ <- Managed.from(new ApiServer(settings))

    // once the ApiServer is started, the HealthCheckServer can show it's ready
    _ <- Managed.eval(healthCheckServer.markReady())(healthCheckServer.markUnready())

    // evalSetup and evalTeardown allow for side-effects during only setup or only teardown
    _ <- Managed.evalSetup(println("Startup is finished!"))
  } yield ()

  // builds the Managed stack and registers a JVM shutdown hook to do automatic teardown
  server.useUntilShutdown()
}

case class Settings(healthCheckPort: Int, apiPort: Int)

class HealthCheckServer(settings: Settings) {
  println("Started HealthCheckServer")
  def stop(): Unit = {
    println("Stopped HealthCheckServer")
  }
  def markReady(): Unit = println("Marked HealthCheckServer Ready")
  def markUnready(): Unit = println("Marked HealthCheckServer Unready")
}

class ApiServer(settings: Settings) extends AutoCloseable {
  import scala.concurrent.ExecutionContext.Implicits.global
  import scala.concurrent.duration.Duration
  import scala.concurrent.Await
  import scala.concurrent.Future
  val fakeServer = new Thread {
    override def run: Unit = {
      Await.ready(Future.never, Duration.Inf)
    }
  }
  fakeServer.start()
  println("Started ApiServer")
  def close(): Unit = {
    println("Stopped ApiServer")
  }
}