giraffe-fsharp / Giraffe

A native functional ASP.NET Core web framework for F# developers.
https://giraffe.wiki
Apache License 2.0
2.13k stars 266 forks source link

Health Checks #462

Open DigitalFlow opened 3 years ago

DigitalFlow commented 3 years ago

Hi there,

I recently switched to Giraffe for my F# API needs and I am amazed so far. We started on a simple .NET Core F# setup and Giraffe is very much an approvement.

What I am wondering however and what the docs don't seem to point out: How to wrap the Health checks that you might have constructed with plain .NET Core?

Before it looked like this:

let configureServices (services : IServiceCollection) =
        services.AddCors()    |> ignore
        services.AddGiraffe() |> ignore

        services
            .AddHealthChecks()
            .AddCheck<BasicHealthCheck>("Basic_Health")
            .AddCheck<CdsHealthCheck>("Cds_Health_Check")
        |> ignore

and a health check looked like this:

namespace FunctionalWebAPI.Domain

open System.Threading.Tasks
open Microsoft.Extensions.Diagnostics.HealthChecks

type BasicHealthCheck() =
    interface IHealthCheck with
        member __.CheckHealthAsync(context, cancellationToken) =
            async { return new HealthCheckResult(HealthStatus.Healthy) }
            |> Async.StartAsTask

Now with Giraffe I changed it to this:

module FunctionalWebAPI.Domain.BasicHealthCheck

open Giraffe
open Microsoft.AspNetCore.Http
open FSharp.Control.Tasks.V2.ContextInsensitive
open Microsoft.Extensions.Diagnostics.HealthChecks

let getBasicHealth =
    fun (next : HttpFunc) (ctx : HttpContext) ->
        task {
            return! json (HealthCheckResult(HealthStatus.Healthy)) next ctx
        }

and route:

route "/health" >=> getBasicHealth

That works for one simple health check. But now I have multiple ones. I tried to "fish my way through" like this

route "/health" >=> getBasicHealth >=> getExtendedHealth

but it is never executing "getExtendedHealth" , probably because getBasicHealth already returns a full json response. In addition to that if composing would work, I would still need to compose the root health status, so an aggregated health of all checks.

There must be an easier way than combining them all myself, right? Thanks for this framework and thanks for your help.

Kind Regards, Florian

dustinmoris commented 3 years ago

What do the different health checks do?

If you want different health checks then you can have multiple endpoints:

/health >=> healthCheck /live >=> liveCheck /ready >=> readyCheck

etc.

If multiple health checks share some code then move your code into a reusable function or dependency which can be called by all the different checks. I don't understand why there is a complete different abstraction for health checks when they are just some simple endpoints no different than all other code.

My honest recommendation would be to implement health endpoints like a normal endpoint and apply normal software patterns for code sharing and reusability if that is needed. It's going to be more readable and similar to the rest of the web app code which will make it easier to understand and maintain.

DigitalFlow commented 3 years ago

Hi @dustinmoris,

thanks for your response.

They do not share code, but the thought was that I would apply this railway programming pattern, so that I return "Healthy" when getBasicHealth and getExtendedHealth both return healthy, but if getBasicHealth fails, I directly return "Unhealthy".

Kind Regards, Florian