Usage guide


For sbt:

libraryDependencies += "com.kubukoz" %% "sup-core" % "0.7.0"

For ammonite:

import $ivy.`com.kubukoz::sup-core:0.7.0`


import sup._

Core concepts


Health is a boolean-like health status. In ADT notation:

data Health = Sick | Healthy

It has a commutative monoid, which is equivalent to the “all” monoid for booleans.

That means Health values can be combined with the following semantics:

|+| Sick Healthy
Sick Sick Sick
Healthy Sick Healthy


type HealthResult[H[_]] = H[Health]

HealthResult is a wrapper over a collection H of Health. There are no limitations about what kind of collection that must be, but it’s recommended that it has a cats.Foldable instance. For a single Health, cats.Id can be used.

Other examples of a suitable type include:

  • cats.Id: there’s only one result.
  • sup.Tagged[String, ?]: there’s only one result, tagged with a String (e.g. the dependency’s name)
  • there are multiple checks
  •[cats.Id,, ?]: there’s one check, and a NonEmptyList of checks
  •[sup.Tagged[String, ?], NonEmptyList, ?]: there’s one check, and a NonEmptyList of checks tagged with a String.

([F, G, ?] is equivalent to[Nested[F, G, ?], ?])

HealthResult[H] has a Monoid for any H[_]: Applicative, although most of its usages will be transparent to the user.


trait HealthCheck[F[_], H[_]] {
  def check: F[HealthResult[H]]

HealthCheck[F, H] is a health-checking action with effects of type F that’ll result in a collection H of Health.

Similarly to HealthResult, a HealthCheck[F, H] has a monoid for any F[_]: Applicative, H[_]: Applicative. This is really cool, because thanks to this we can combine two similar healhchecks into one that’ll check both (sequentially or in parallel, depending on the applicative of F - to enforce parallel execution use HealthCheck.parTupled).

Let’s start with some cats imports (assume they’re available in the rest of the page):

import cats._,, cats.effect._, cats.implicits._

implicit val contextShift: ContextShift[IO] = IO.contextShift(
implicit val timer: Timer[IO] = IO.timer(

and here’s how healthchecks can be combined:

//will always be Sick
def queue1: HealthCheck[IO, Id] = HealthCheck.const(Health.Sick)

//will always be Healthy
def queue2: HealthCheck[IO, Id] = HealthCheck.const(Health.Healthy)

//will always be Sick
def queues = queue1 |+| queue2

Advanced concepts

In sup, a single dependency’s healthcheck has the same underlying structure as a whole service’s healthcheck consisting of multiple dependencies’ checks.


A healthcheck wrapping multiple healthchecks is called a HealthReporter. Here’s how it’s defined in sup:


type HealthReporter[F[_], G[_], H[_]] = HealthCheck[F, Report[G, H, ?]]

You can construct one from a sequence of healthchecks using the HealthReporter.fromChecks function:

import //Import actual HealthReporter //Import actual HealthReporter

val kafka: HealthCheck[IO, Id] = HealthCheck.const(Health.Healthy)
// kafka: HealthCheck[IO, Id] = sup.HealthCheck$$anon$1@3e7c8749
val postgres: HealthCheck[IO, Id] = HealthCheck.const(Health.Healthy)
// postgres: HealthCheck[IO, Id] = sup.HealthCheck$$anon$1@1fa7df6e

val reporter: HealthReporter[IO, NonEmptyList, Id] = HealthReporter.fromChecks(kafka, postgres)
// reporter: HealthReporter[IO, NonEmptyList, Id] = sup.HealthCheck$$anon$1@566f78a7


A healthcheck can be tagged with a label, e.g. a String with the dependency’s name:

val kafkaTagged = kafka.through(mods.tagWith("kafka"))
// kafkaTagged: HealthCheck[IO,[String, H]] = sup.HealthCheck$$anon$1@77c26012
val postgresTagged = postgres.through(mods.tagWith("postgres"))
// postgresTagged: HealthCheck[IO,[String, H]] = sup.HealthCheck$$anon$1@5b151188

val taggedReporter = HealthReporter.fromChecks(kafkaTagged, postgresTagged)
// taggedReporter: HealthReporter[IO, NonEmptyList,[String, H]] = sup.HealthCheck$$anon$1@75055e66


sup provides a variety of ways to customize a healthcheck. These include through, mapK, transform, mapResult and leftMapK. Check out the predefined modifiers in the sup.mods object or create your own.

Here are some example modifiers provided by sup:


A check modified with timeoutToSick will be marked as sick if it doesn’t complete within the given duration.

import scala.concurrent.duration._

val timedKafka = kafka.through(mods.timeoutToSick(5.seconds))
// timedKafka: HealthCheck[IO, Id] = sup.HealthCheck$$anon$1@7be7a3f7

Other modifiers with timeouts include timeoutToDefault and timeoutToFailure.

tagWith / untag

Tag a healthcheck with a value (or unwrap a tagged healthcheck):

val taggedKafka2 = kafka.through(mods.tagWith("foo"))
// taggedKafka2: HealthCheck[IO,[String, H]] = sup.HealthCheck$$anon$1@4e571daa

val untaggedKafka = taggedKafka2.through(mods.untag)
// untaggedKafka: HealthCheck[IO, Id] = sup.HealthCheck$$anon$1@563a4b29


Swallows any errors that might happen in the healthcheck’s effect and falls back to Sick.

val safeKafka = kafka.through(mods.recoverToSick)
// safeKafka: HealthCheck[IO, Id] = sup.HealthCheck$$anon$1@2435960


Surrounds a healthcheck with effectful actions.

val surroundedKafka = kafka.through(mods.surround(IO(println("foo")))(result => IO(println(s"foo result: $result"))))
// surroundedKafka: HealthCheck[IO, Id] = sup.HealthCheck$$anon$1@4517ed44