Computation Expression Approach For Calling Rest Api

Computation expression approach for calling REST API

In this post, I will share with you how I handle latency and exception for Ajax calls from a front-end Single page application built in F# with WebSharper. The code is inspired by @ScottWlaschin blog post http://fsharpforfunandprofit.com/posts/elevated-world-5/#asynclist. I will not explain what is a computation expression, for that you can refer to fsharpforfunandprofit computation expressions tutorial and I will not explain how to use WebSharper and UI.Next, for that you can refer to UI.Next samples. What I will explain here is how I combine both; to write simple code without worrying about side-effects.

When interacting with REST API from a Single page application, it is a common pattern to:

  • Get an authentication token with a Ajax call
  • Retrieve some JSON data using the token with a Ajax call (again)
  • Deserialize the content from JSON to your local type

What we would want to write is (1)

let getMyData () =
    let token = Api.getToken()
    let data = Api.getData token
    return data

That is great and we all wished it was that simple. But what we forgot is that:

  1. Api module methods are hitting the REST API, thus they have to be asynchronous
  2. Any Api module call can potentially fail for any reason (e.g. 401, 404, 500 http status)
  3. Parsing the JSON may fail if the JSON is malformed

Based on this points, we can see that we are facing two side effects: latency and exception. Latency is handled by transforming a function to an asynchronous function which returns an Async<'a> result. Exception will be handled by a special type that we will define and call ApiResult<'a>. It will either be a Success or a Failure. By making side effects explicit, we end up with the following function definition:

//Started with
unit -> DataType
//Ended up with
unit -> Async<ApiResult<DataType>>

So what can we do with Async<ApiResult<'a>> type? A lot! Thanks to computation expression in F#, we can manipulate the underlying type DataType directly without thinking of Async orApiResult! In other words, we can write code without worrying about latency and exception. Doing this will allow us to write the code we saw in (1).

Making the computation expression

Just like the async computation expression with async {...} workflow, we want to define our own apiCall computation expression which will allow us to use apiCall {...} notation to write workflows which handle asynchronous calls and exceptions. But where do we start? Here’s what we need to do:

  1. Define ApiResult
  2. Make our ApiCallBuilder which is a type that allows us to instantiate the computation expression (’…Builder’ is just a convention, you could have it called ‘Hello’ and it would have worked the same way)
  3. Instantiate the builder apiCall and use it!

So let’s start by defining what is an ApiResult.

Defining ApiResult

ApiResult will capture every possible outcomes of our Api calls and Json deserialization. Like what we saw earlier, the api may throw an error or the json may throw an error. So we define it like follows:

type ApiResult<'a> =
| Success of 'a
| Failure of ApiResponseException list
and ApiResponseException =
    | Unauthorized of string
    | NotFound of string
    | UnsupportedMediaType of string
    | BadRequest of string
    | JsonDeserializeError of string
    with
        override this.ToString() =
            match this with
            | ApiResponseException.Unauthorized err -> err
            | ApiResponseException.NotFound err -> err
            | ApiResponseException.UnsupportedMediaType err -> err
            | ApiResponseException.BadRequest err -> err
            | ApiResponseException.JsonDeserializeError err -> err

This type specifies that ApiResult is either a Success or Failure where the Failure can be any of the followings: Unauthorized, NotFound, UnsupportedMediaType, BadRequest, JsonDeserializeError.

Defining ApiCallBuilder

To build a computation expression, two methods are required Bind and Return (there are a bunch of them, you can go ahead and implement all if you want. It will give you more power in the workflow which is within the curly braces https://msdn.microsoft.com/en-us/library/dd233182.aspx). So we define it like that:

type ApiCallBuilder() =
    member this.Bind(m, f) =
        bind f m
    member this.Return x =
        retn x

Obviously, we don’t know what is bind and retn so let’s define it:

let retn x = async { return ApiResult.Success x }
// 'a -> Async<ApiResult<'a>>

let bind f m =
    async {
        let! xApiRes = m
        match xApiRes with
        | Success x -> return! f x
        | Failure err -> return Failure err
    }
//('a -> Async<ApiResult<'b>>) -> Async<ApiResult<'a>> -> Async<ApiResult<'b>>

Within the workflow:

  • let! unwrapps the special type. In let! xApiRes = m, m is of type Async<ApiResult<'a>> and xApiRes is of type ApiResult<'a>. See what we did there? let! successfully transformed Async<ApiResult<'a>>' to ApiResult<'a>, it took away Async!
  • return and return! let you exit the workflow and return a result. The difference is that return lets you pass a underlying type 'a' and exits with a wrapped type Async<'a>' whereas return! lets you directly pass wrapped type Async<'a'> and exits with the same wrapped type Async<'a'>.

To define the builder we used retn and bind.

  • retn takes a normal input of 'a and tranforms it to a special type of Async<ApiResult<'a>>.
  • bind is used to transform a function that takes a normal input and returns a special type 'a -> Async<ApiResult<'b>> to a function that takes a special type and return a special type Async<ApiResult<'a>> -> Async<ApiResult<'b>>.

Alright, we’ve done great so far. We are done with the ApiCallBuilder. We can now instantiate it and use it as a keyword!

let apiCall = new ApiCallBuilder()

And we can use it like so:

let getMyData () =
    apiCall {
        let! token = Api.getToken() //token: string
        let! data = Api.getData token //data: DataType
        return data
    }
    //unit -> Async<ApiResult<DataType>>

Amazing! We are now manipulating simple types: string and our ownDataType instead of Async<ApiResult<string>> and Async<ApiResult<DataType>>. This is what we wanted to achieve at the beginning. Isn’t it wonderful? We used the computation expression to take-out latency and exception but you can now go ahead and create your own computation expressions which fit your scenarios to abstract side-effects like logging or persistence or anything else you like.

Where’s the WebSharper

At the start I said I will make a Ajax call from a SPA with WebSharper so let’s do it.

First I will define a general Ajax call method:

let ajaxCall reqType url contentTy headers data =
        Async.FromContinuations
        <| fun (ok, ko, _) ->
            let settings = JQuery.AjaxSettings(
                            Url = "http://localhost/api/" + url,
                            Type = reqType,
                            DataType = JQuery.DataType.Text,
                            Success = (fun (result, _, _) ->
                                        ok (result :?> string)),
                            Error = (fun (jqXHR, _, _) ->
                                      ko (System.Exception(string jqXHR.Status)))
                            )
            headers   |> Option.iter (fun h -> settings.Headers <- h)
            contentTy |> Option.iter (fun c -> settings.ContentType <- c)
            data      |> Option.iter (fun d -> settings.Data <- d)
            JQuery.Ajax(settings) |> ignore

This method creates the AjaxSettings which contains all the information about the http request and post it using JQuery.Ajax(settings). Async.FromContinuations is used to turn a method which use Success and Error callbacks to a function which returns an Async result.

We also define utility methods to convert http code to Failure and to deserialize Json string to ApiResult.

let private matchErrorStatusCode url code =
    match code with
    | "401" -> Failure [ ApiResponseException.Unauthorized
                         <| sprintf """"%s" - 401 The Authorization header did not pass security""" url]
    | "404" -> Failure [ ApiResponseException.NotFound
                         <| sprintf """"%s" - 404 Endpoint not found""" url ]
    | "415" -> Failure [ ApiResponseException.UnsupportedMediaType
                         <| sprintf """"%s" - 415 The request Content-Type is not supported/invalid""" url]
    | code -> Failure [ ApiResponseException.BadRequest
                        <| sprintf """"%s" - %s Bad request""" url code ]

let tryDeserialize deserialization input =
    match input with
    | Success json ->
        try
            deserialization json |> ApiResult.Success
        with _ -> Failure [ ApiResponseException.JsonDeserializeError
                            <| sprintf """"{%s}" cannot be deserialized""" json ]
    | Failure err -> Failure err
    |> Async.retn
//(string -> 'a) -> ApiResult<string> -> Async<ApiResult<'a>>

And we define two new types to handle the authentication and token.

type AuthToken = AuthToken of string
type Credentials = {
    UserName:string
    Password:string
} with
    static member Default = { UserName = "admin"; Password = "admin" }

We use the type AuthToken to represent the token. It allows us to type the argument of methods expecting a string token and take an AuthToken instead. Credentials is a record type which holds the user name and password.

Great now that we have our foundation for Ajax calls, let’s make the first method getToken.

let getToken () =
    let url = "auth/login/token"
    async {
        try
            let! token = ajaxCall JQuery.RequestType.POST url (Some "application/json") None (Some (Json.Serialize(Credentials.Default)))
            return ApiResult.Success token
        with ex -> return matchErrorStatusCode url ex.Message
    } |> Async.bind (tryDeserialize (Json.Deserialize<string> >> AuthToken))
//unit -> Async<ApiResult<AuthToken>>

The Ajax call returns a Async<string> but it can throw exception so we want to make the exception explicit in the type. In order to do that, we try catch the Ajax call and return a Async<ApiResult<string>> which will take in account the exception. If you remember, an ApiResult is either a Success or a Failure and in the case of a Failure, it can be Unauthorized or NotFound etc… After that we pipe it to the deserialization with tryDeserialize which in case of error, returns a Failure of JsonDeserializeError. We do the same for getData.

let getData (AuthToken token) =
    let url = "data"
    async {
        try
            let! data = ajaxCall JQuery.RequestType.GET url None (Some (Object<string> [|("Authorization", "Bearer " + token)|])) None
            return ApiResult.Success (data)
        with ex -> return matchErrorStatusCode url ex.Message
    } |> Async.bind (tryDeserialize Json.Deserialize<MyData>)

Finally we can use getToken and getData in a apiCall workflow and bind it to a button in our HTML template.

type IndexTemplate = Template<"index.html">

let Main =
  JQuery.Of("#main").Empty().Ignore

  let rvData = Var.Create ""

  let getData = apiCall {
                  let! token = ApiClient.getToken ()
                  let! data = ApiClient.getData token
                  return data
                }

  let GetDataBtn =
    div [ Doc.Button "Get data" []
          <| fun _ -> async {
                        let! data = getData
                        do match data with
                           | Success data -> sprintf "%A" data
                                             |> Var.Set rvData
                           | Failure exs -> exs |> List.map string
                                                |> String.concat "-"
                                                |> JS.Alert
                      } |> Async.Start ]


  IndexTemplate.Main.Doc(Action = [ GetDataBtn ], Content = rvData.View)
  |> Doc.RunById "main"

In getData we see how to use our apiCall keyword to write nice code to get the token and then use it to get the data. And we are done! What we have built is a complete recipe to interact with our REST Api from WebSharper front-end. The code looks simple now (I think…) but it handles asynchronous calls and handles exception in the background! If we need to make other calls to the Api, we can just write it inside an apiCall {...} block and we get async and error handling for free!

And here is the HTML.

<div id="main" data-children-template="Main">
  <div class="row">
    <div class="large-6 columns" data-hole="Action"></div>
    <div class="large-6 columns">
      $!{Content}
    </div>
  </div>
</div>

Conclusion

Today we explored how to create a computation expression in F# and how we could use it to write simple code which would abstract latency and exception. We also learnt how to query a REST Api from a WebSharper SPA using WebSharper.JQuery and how to deserialize the result using WebSharper.Json. I hope you enjoyed this post as much as I enjoyed writting it. I am by no mean an expert, in fact I just started to learn about F# two months ago and WebSharper a month ago and I love it! So if you find any errors or if you feel that this is not the right way, please share with me your remarks. Thanks for reading!

Links

Comments

Popular posts from this blog

A complete SignalR with ASP Net Core example with WSS, Authentication, Nginx

Verify dotnet SDK and runtime version installed

SDK-Style project and project.assets.json