kubukoz/sup

Generalisation of single or compound health checks

ludovicc opened this issue · 3 comments

Hello,

I find it hard to generalise between single health checks of type HealthCheck[F, TaggedS] and compound checks of type HealthReporter[F, NonEmptyList, TaggedS] where type TaggedS[H] = Tagged[String, H]

I just want to call the check and report errors, but the different types seem to go in the way. Can you propose a better solution than this code duplication?

Thanks


def dbHealth: Route = pathPrefix("db") {
    pathEndOrSingleSlash {

      get {
        // type TaggedS[H] = Tagged[String, H]
        // healthChecks: HealthReporter[F, NonEmptyList, TaggedS]
        onComplete(runLater(databaseServices.healthChecks.check)) {
          case Success(checks) =>
            if (checks.value.health.isHealthy)
              complete(OK)
            else
              complete((StatusCodes.InternalServerError, checks.value.show))
          case Failure(ex) =>
            complete((InternalServerError, s"An error occurred: ${ex.getMessage}"))
        }
      }
    } ~ featuresDbHealth ~ metaDbHealth ~ wokenDbHealth
  }

private def performCheck(check: HealthCheck[F, TaggedS]): Route =
    onComplete(runLater(check.check)) {
      case Success(checks) =>
        if (checks.value.health.isHealthy)
          complete(OK)
        else
          complete((StatusCodes.InternalServerError, checks.value.show))
      case Failure(ex) =>
        complete((InternalServerError, s"An error occurred: ${ex.getMessage}"))
}

https://github.com/LREN-CHUV/woken/blob/master/src/main/scala/ch/chuv/lren/woken/api/MonitoringWebService.scala#L182
https://github.com/LREN-CHUV/woken/blob/master/src/main/scala/ch/chuv/lren/woken/api/MonitoringWebService.scala#L217

Off the top of my head, you should be able to abstract on the container type of your HealthCheck like HealthCheck[F, Lambda[A => G[TaggedS[A]]]], and most functions like isHealthy would work if G[_]: Foldable. In case you need more info or anything turns out not to be true (I haven't looked at sup in a while), I'll get back to this and look for alternatives.

This seems to compile in your case:

private def performCheck[G[_]: cats.Foldable](check: HealthCheck[F, G])(implicit show: cats.Show[G[sup.Health]]): Route =
  onComplete(runLater(check.check)) {
    case Success(checks) =>
      if (checks.value.combineAll.isHealthy)
        complete(OK)
      else
        complete((StatusCodes.InternalServerError, checks.value.show))
    case Failure(ex) =>
      complete((InternalServerError, s"An error occurred: ${ex.getMessage}"))
  }

I think I might also be missing an implicit Show[G[Health]] => Show[HealthResult[G]] instance that'd make this a tiny bit more helpful. And it'd be much nicer if we had a higher-kinded, universally quantified Show[G[a] forall a]...

(do consider some json encoder like Circe's instead of Show though)

Let me know if doesn't work :) in fact, if it does and you want to contribute, I'd accept a PR with routing code like the http4s module (https://github.com/kubukoz/sup/blob/master/modules/http4s/src/main/scala/sup/modules/http4s.scala#L34)

I'll close this issue for now. If you still have problems generalizing the code, feel free to ping me again and reopen this :)