Being REST-like in WebSharper – Part 2

      1 Comment on Being REST-like in WebSharper – Part 2

Today I will take up where I left off last time and flesh out my PizzaManager REST-like API a bit. But before I start, I promised last time that I would expand a little on the topic of RESTful APIs.

Is my API really RESTful?

For a few years now, we’ve seen an increasing number of web APIs boasting to be RESTful, providing nice and readable URLs for their resources, like the ones below:

  • GET api/employee/12
  • GET api/employees/all
  • POST api/employee { “firstName”: “John”, “lastName”: “Doe” }
  • DELETE api/documents/all

Are those APIs really RESTful though? We talked very quickly last time about the Richardson Maturity Model. Basically, The model says that they are 3 levels of RESTfulness:

  • Level 1: usage of resources. This is the part about how the URIs are constructed. Instead of querying an employee with a good ol’ query string (api/getEmployee/?id=12), we design a system where each resource has its own URI: api/employee/12
  • Level 2: usage of verbs. This is the part about using different Http Verbs like GET, POST, PUT or DELETE to express the action we want to apply on a given resource. So instead of having two queries like api/getEmployee/?id=12 and api/deleteEmployee/?id=12, we can simply define one and interchange the verbs as needed: GET api/employee/12 versus DELETE api/employee/12.
  • Level 3: usage of hypermedia controls. In short, this part is about how to make your API discoverable by communicating with hypermedia links rather than just sending back some data. For instance, if you were to create a new employee using the following request: POST api/employee { “firstName”: “John”, “lastName”: “Doe” }, the response would not only contain data about the newly created resource, but also other links to manipulate it, like DELETE api/employee/johnDoe or GET api/contactInfo/johnDoe/address. This allows to explore the API as you go along, from one link to another.

The problem is that according to the inventor of REST himself, Roy Fielding, an API cannot be deemed RESTful unless it achieves all 3 levels! However, the vast majority of APIs in the wild “only” achieve level 1 and level 2, like my own API below. In fact, achieving level 3 can be a very complex task on its own. Is it really worth making the distinction? Well, since truly RESTful APIs offer a rather different way to be navigated and explored, I’d say it is rather important to state the difference, for the sake of communication and to avoid any surprise when integrating with such APIs. I’ll let you make your own opinion on the topic.

Time to go go back to our PizzaManager!

Fixing the ugly Json generated

If you remember, I wasn’t really happy about how the generated Json looked like:

{
  "result": "success",
  "name": "Margherita",
  "price": 13,
  "ingredients": [
    {
      "name": "Tomato Sauce",
      "quantity": {
        "$": 2,
        "Item": 30
      }
    },
    {
      "name": "Mozzarella",
      "quantity": {
        "$": 1,
        "Item": 200
      }
    }
  ]
}

So decided to refactor it a bit. This is how our new pizza’s Json looks like now:

{
  "result": "success",
  "data": {
    "name": "Margherita",
    "price": 12,
    "ingredients": [
      {
        "unit": "ml",
        "name": "Tomato Sauce",
        "quantity": 30
      },
      {
        "unit": "g",
        "name": "Mozzarella",
        "quantity": 200
      }
    ]
  }
}

Much better! How did I achieve this? Let’s look at the F# types below:

[<NamedUnionCases"unit">]
type Ingredient =
  | [<Name "u">] Unit of name:string * quantity:int
  | [<Name "g">] Grams of name:string * quantity:int
  | [<Name "ml">] Milliliter of name:string * quantity:int

type Pizza =
  { [<Name"name">] Name : string
    [<Name"price">] Price : decimal
    [<Name"ingredients">][<OptionalField>] Ingredients : Ingredient array }

First, I refactored Ingredient so all data is contained in a single structure (name, unit and quantity). Each unit of measure (Unit, Grams and Milliliters) are now associated with a tuple containing both name and quantity of the given ingredient. A pizza can now be declared as follows:

{ Name = "Margherita"; Price = 12M; Ingredients = [|
    Milliliters("Tomato Sauce", 30)
    Grams("Mozzarella", 200) |]
}

Secondly, I took advantage of  the NamedUnionCases and Name attributes provided by WebSharper to further improve how the Json is generated. It was overall a very easy change to make.

I also added a few new actions to the API:

type PublicApi =
    /// GET /info
    | [<EndPoint"GET /">] ShowInfo
    /// GET /pizzas/{settings}
    | [<EndPoint"GET /pizzas">] GetPizzas of settings : string
    /// GET /pizza/margarita
    | [<EndPoint"GET /pizza">] GetPizza of name : string
    /// DELETE /pizza/margarita
    | [<EndPoint "DELETE /pizza">] DeletePizza of name : string
    /// POST /pizza/ + Json content
    | [<Method "POST"; CompiledName "pizza"; Json "pizzaData">] AddPizza of pizzaData : Pizza

Let’s go through the most noticeable changes quickly.

The ShowInfo action

The ShowInfo action is available under the following link: http://localhost:9000/pizzamanager/
It allows to see what are the different actions available in the API. Here’s how it looks in Postman:

The actual implementation is trivial and a bit naive, as it has to be maintained manually when adding new actions. It will do for now though. Here’s the code behind it:

/// Display info about the current API
type ApiInfo = { sampleUrl : string; description : string }

let showInfo =
    [|
        { sampleUrl = "[GET] /pizzamanager or /pizzamanager/"; description = "Display information about the current API." }
        { sampleUrl = "[GET] /pizzamanager/pizzas/names"; description = "List all pizzas available in the database." }
        { sampleUrl = "[GET] /pizzamanager/pizzas/ or /pizzamanager/pizzas/all"; description = "Get all pizzas in the database." }
        { sampleUrl = "[GET] /pizzamanager/pizza/{pizza_name}"; description = "Get the given pizza (case-insensitive)." }
        { sampleUrl = "[DELETE] /pizzamanager/pizza/{pizza_name}"; description = "Delete the given pizza from the database (case-insensitive)." }
    |]

// And the callsite from the requestHandler function below
| ShowInfo -> Content.Json (showInfo)

The updated GetPizzas action with settings

I decided to add a new settings parameter to the GetPizzas action, so it returns either the full details for all pizzas, or just the list of pizzas available in the database. You can check the final result in the .gif below:

(click on the .gif to enlarge)
The implementation was once again trivial:

/// Handle additional settings available for /pizzamanager/pizzas/
type ListSettings =
| All
| NamesOnly

let getSettings = function
    | "" | "all" -> Success All
    | "names" -> Success NamesOnly
    | notRecognized -> Failure (sprintf "'%s' is not a valid setting!" notRecognized)

// And the callsite from the requestHandler function below
| GetPizzas settings -> 
    match (getSettings settings) with
    | Success All -> Content.Json (PizzaManagerDb.getAll())
    | Success NamesOnly -> Content.Json (PizzaManagerDb.getNamesOnly())
    | failure -> Content.Json (failure)

The getSettings function has an interesting syntax here. It is what we call a pattern matching function and could also be written as below:

let getSettings2 settings =
    match settings with
    | "" | "all" -> Success All
    | "names" -> Success NamesOnly
    | notRecognized -> Failure (sprintf "'%s' is not a valid setting!" notRecognized)

However I prefer the conciseness of the first version!

The DeletePizza action

The URI of the DeletePizza endpoint looks almost like the one for GetPizza:

/// GET /pizza/margarita
| [<EndPoint"GET /pizza">] GetPizza of name : string
/// DELETE /pizza/margarita
| [<EndPoint "DELETE /pizza">] DeletePizza of name : string

The only difference is the latter uses the DELETE Http verb while the former uses GET. Making proper use of the verbs available is one of the features necessary to achieve RESTness. Your URLs designate the resources while the verbs indicate the operation:

GET /pizzamanager/pizza/rucola
vs
DELETE /pizzamanager/pizza/rucola

The AddPizza action

[EDIT: 02/05/2017] The issue described in the paragraph below is now solved! And it was indeed not related to WebSharper itself but to a mistake I made when configuring how the requests are being handled (more details here).

Unfortunately, I wasn’t able to properly make the AddPizza action work! I spent a few hours trying to figure out why, without success. I explained the problem here on WebSharper’s forums, hopefully someone will be able to help with that. Fingers crossed! In short, it seems that WebSharper is not able to probably route to the AddPizza action and a 404 Not Found status code is returned:


Wrapping up

Let’s see a short demo of all the working actions so far:

To sum up today’s post, I am really happy about how easy and quick it is to generate a decent REST-like API using WebSharper. However I still need to figure out why the AddPizza action is not working properly. I will update this post if and when I get more information about it.
As explained below, the issue was due to a configuration mistake in Owin and not to WebSharper itself. This also gave us the opportunity to check if WebSharper’s support works, and at least in our case it did!

Cheers

One thought on “Being REST-like in WebSharper – Part 2

  1. Pingback: Load-testing the PizzaManager REST-like API with JMeter – Youenn Bouglouan

Leave a Reply

Your email address will not be published. Required fields are marked *