Creating a Functional Wrapper in F#
Tags: functional, fsharp, csharp, dotnet
Published: 2020-07-27 02:53:26

One of the big selling points of F# is the wealth of .NET libraries available to you, both in the Base Class Library and NuGet.

Many of these libraries, however, are developed primarily with C# in mind. They can usually be consumed without issue, but I often find it helpful to write a little wrapper around them to provide a more idiomatic, functional interface. (Plus, it never hurts to build a facade around a third-party dependency!)

Let's walk through how we might build a wrapper around `HttpClient` from the `Microsoft.Extensions.Http` package.

To start off, here's how we could use `HttpClient` to make a `GET` request:

```fsharp
let (statusCode, body) =
    async {
        use httpClient = new HttpClient()

        httpClient.Timeout <- TimeSpan.FromSeconds 2.

        use requestMessage =
            new HttpRequestMessage(
                HttpMethod.Get,
                "https://hacker-news.firebaseio.com/v0/item/8863.json"
            )

        requestMessage.Headers.Add("Accept", "application/json")

        use! response = httpClient.SendAsync(requestMessage) |> Async.AwaitTask
        let! body = response.Content.ReadAsStringAsync() |> Async.AwaitTask

        return (response.StatusCode, body)
    }
    |> Async.RunSynchronously
```

This isn't awful, but I think we can do better. It'd be nice to encapsulate some of these details and make the code a bit more declarative.

Let's start by defining some types for the interface that we want:

```fsharp
type HttpMethod =
| Get
| Delete

type Request = {
    Url : string
    Method : HttpMethod
    Timeout : TimeSpan option
    Headers : (string * string) list
}
```

For now, we'll just support `GET`s and `DELETE`s, but it will be easy to add support for other methods as we need to. We'll require the user to specify a `Url` and `Method`, but `Timeout` and `Headers` are optional.

For our response, we can just capture the status code and body for now:

```fsharp
type Response = {
    StatusCode : int
    Body : string
}
```

Now that we have our types, let's think about what our function signature might look like:

```fsharp
module Http =
    let execute (httpClientFactory : IHttpClientFactory) (request : Request) : Async<Response> = ...
```

We'll take in an instance of `IHttpClientFactory` [^1] and `Request`, and return an `Async<Response>`.

Let's see how ergonomic it would be to use at this point:

```fsharp
let request =
    {
        Url = "https://hacker-news.firebaseio.com/v0/item/8863.json"
        Method = Get
        Timeout = Some (TimeSpan.FromSeconds 2.)
        Headers = [ ("Accept", "application/json") ]
    }

let response =
    request
    |> Http.execute httpClientFactory
    |> Async.RunSynchronously
```

Not too bad. One thing to note is that even if we don't want to provide a `Timeout` or `Headers`, we still have to set the properties:

```fsharp
let request =
    {
        Url = "https://hacker-news.firebaseio.com/v0/item/8863.json"
        Method = Get
        Timeout = None
        Headers = []
    }
```

Let's create a helper function to make a request with some default values:

```fsharp
module Http =
    let createRequest url method =
        {
            Url = url
            Method = method
            Timeout = None
            Headers = []
        }
```

Now we have:

```fsharp
let request = Http.createRequest "https://hacker-news.firebaseio.com/v0/item/8863.json" Get
let requestWithTimeout = { request with Timeout = Some (TimeSpan.FromSeconds 2.) }
let requestWithTimeoutAndHeaders = { requestWithTimeout with Headers = [ ("Accept", "application/json") ] }
```

Since records are immutable, we use `with` to create a new instance with some additional properties set.

This is more tedious than just creating the `Request` ourselves and setting all the properties. Let's create some more helper functions for adding a timeout and headers:

```fsharp
[<AutoOpen>]
module Request =
    let withTimeout timeout request =
        { request with Timeout = Some timeout }

    let withHeader header request =
        { request with Headers = header :: request.Headers }
```

Notice that `request` comes in as the last parameter. This is important to enable pipelining:

```fsharp
let request =
    Http.createRequest "https://hacker-news.firebaseio.com/v0/item/8863.json" Get
    |> withTimeout (TimeSpan.FromSeconds 2.)
    |> withHeader ("Accept", "application/json")
```

Pretty slick, right? We've created a little domain-specific language (DSL) to describe the different options, and we can pick and choose what we need.

## From Overloads to DSLs ##

In C# land, you might use overloads, optional parameters, and mutable properties to capture the different configuration options. If you layer on dot chaining and some well-designed methods, you can create a fluent builder.

In F#, there's a strong focus on creating DSLs, using features like records, the pipeline operator, discriminated unions[^2], and computation expressions[^3]. We've created a builder here, not too different than something like LINQ, though it uses function pipelines instead of dot chaining.

## Implementing 'execute' ##

Now that we've talked about the interesting bits, we can see what the implementation of our `execute` function might look like:

```fsharp
module HttpMethod =
    let value method =
        match method with
        | Get -> System.Net.Http.HttpMethod.Get
        | Delete -> System.Net.Http.HttpMethod.Delete

module Http =
    let execute (httpClientFactory : IHttpClientFactory) (request : Request) : Async<Response> =
        async {
            use httpClient = httpClientFactory.CreateClient()

            request.Timeout
            |> Option.iter (fun t -> httpClient.Timeout <- t)

            use requestMessage = new HttpRequestMessage(request.Method |> HttpMethod.value, request.Url)

            request.Headers
            |> List.iter requestMessage.Headers.Add

            use! response = httpClient.SendAsync(requestMessage) |> Async.AwaitTask
            let! body = response.Content.ReadAsStringAsync() |> Async.AwaitTask

            return
                {
                    StatusCode = int response.StatusCode
                    Body = body
                }
        }
```

Not too much different from our initial example. We did add a `HttpMethod.value` function to convert between our representation and `System.Net.Http.HttpMethod`, and we optionally set the timeout via `Option.iter` which only runs the callback if we have a value.

## Modules vs. Objects

Where possible, I tends to use modules and functions over objects. For dependencies that would typically be passed into a class constructor, we just pass to the function directly (like `IHttpClientFactory` in our example). By positioning them at the beginning of the parameter list, you'll be able to use partial application if you want to.

Here's what that might look like with `execute`:

```fsharp
let makeRequest = Http.execute httpClientFactory    //only apply the first parameter

let request =
    Http.createRequest "https://hacker-news.firebaseio.com/v0/item/8863.json" Get
    |> withTimeout (TimeSpan.FromSeconds 2.)
    |> withHeader ("Accept", "application/json")

request
|> makeRequest    //pipe in the final parameter, 'request'
|> Async.RunSynchronously
```

We can even do the same thing with `createRequest` if we want:

```fsharp
let resource = Http.createRequest "https://hacker-news.firebaseio.com/v0/item/8863.json"

let get = resource Get
let delete = resource Delete
```

Perhaps this is overkill, but these are the kinds of things I think about when deciding how to order the parameters — from more general to more specific; dependencies first, data second.

## Conclusion ##

We were able to create a nice wrapper around `HttpClient` that will play nicely with the rest of our code. It was no accident that we spent a good chunk of time upfront thinking about the types and the different interactions that we want our interface to support. That's the tricky part to get right; the implementation usually ends up just doing a bit of translation and delegation to the underlying library. Note that you don't have to expose the entire library — indeed, it's often easier on consumers if you just expose the stuff you actually use.

Here's a [gist](https://gist.github.com/jhewlett/865c11442ab383a5ada5d0c6cad174d1) that shows the full example, including some additional code for handling other HTTP methods, request bodies, and dealing with query strings[^4]. Enjoy!

_Thanks to [Isaac Abraham](https://twitter.com/isaac_abraham) and [Brett Rowberry](https://twitter.com/BrettRowberry) for reviewing the draft of this post!_

[^1]: `IHttpClientFactory` is fairly new and basically acts as a pool for `HttpClient`s. You need to use the built-in DI container to get an instance of it.

[^2]: You _can_ do overloading and optional parameters in F#, too, if you use methods rather than functions. I tend to avoid them because they don't play very nicely with type inference, and [discriminated unions are a great alternative](https://blog.ploeh.dk/2013/10/21/replace-overloading-with-discriminated-unions/).

[^3]: Take a look at [Saturn](https://saturnframework.org/explanations/routing.html) and [Farmer](https://compositionalit.github.io/farmer/api-overview/#putting-it-all-together) for two good examples of using computation expressions to create DSLs.

[^4]: I have successfully used this in production, but the wrapper is by no means exhaustive. Take a look at [FsHttp](https://github.com/ronaldschlenker/FsHttp) for a more fully-featured library.