torsdag, marts 27, 2014

Modelling a Shipment example as a hypermedia service with Mason

Yesterday I was attending the "RAML" workshop at the API Strategy konference. In this workshop the speakers introduced a very simple little Web API; It allowed a customer to GET a (shipment) quote and afterwards create (POST) an actual shipment request based on the quote.

I decided that it could be fun to hypermedia-ize the example and show how it could be represented using the media type Mason. So here we go :-)

The first step is to ask for a quote; the customer has a package of some sort and needs to ship it from A to B, so he access the shipment service and asks for a quote (a price) given the size, weight, origin and destination for the package.

In the original example you could ask for a quote by issuing a GET request to /quote. But I believe that asking for a quote would result in a concrete quote being created and stored in the system as a separate resource to access later on, either by the customer or by the customer service of the shipment company. So I would rather go for a POST of a quote request followed by a redirect to the newly created quote.

At this point we could either document how to POST such a quote - or we could tell the client how to do it using hypermedia controls - and obviously I would go for the later. So lets ask the service for instructions and issue a GET /quote request. The output is a Mason document with suitable hypermedia controls embedded in it:

{
  "@namespaces":
  {
    "myth":
    {
      "name": "http://mythological-shipment.com/api/rel-types"
    }
  },
  "@actions":
  {
    "myth:quote":
    {
      "type": "json",
      "method": "POST",
      "href": "http://mythological-shipment.com/api/quote",
      "title": "Ask for a quote",
      "description": "Ask for a quote by posting package details. Weight is in kilograms, volume in cubic decimeters, origin and destination must be known identifiers for airports.",

      "schemaUrl": "... URL to JSON schema describing the request ..."
    }
  }
}


The client reads this action specification, encodes the package details in JSON and POST it to the URL of the "href" property. As a result the service creates a new quote resource and redirects the client to it:

Request:

  POST http://mythological-shipment.com/api/quote HTTP/1.1
  content-type: application/json


  {
    "weight": 2.3,
    "volume": 4,
    "origin": "CPH",
    "destination": "AMS"
  }

Response:

  201 Created
  Location: http://mythological-shipment.com/api/quotes/myqo-129-gyh

Now the client can GET the newly created quote to get further instructions of how to accept the quote. The result is again a Mason representation of the quote itself plus hypermedia controls for accepting the quote:

{
  "id": "myqo-129-gyh",
  "weight": 2.3,
  "volume: 4,
  "origin": "CPH",
  "destination": "AMS":
  "price": 12,
  "@links":
  {
    "self":
    {
      "href": "http://mythological-shipment.com/api/quotes/myqo-129-gyh"
    }
  },
  "@actions":
  {
    "myth:accept-quote":
    {
      "type": "POST",
      "href": "http://mythological-shipment.com/api/quotes/myqo-129-gyh/state",
      "template":
      {
        "accepted": "yes"
      }
    }
  }
}


As you can see the quote has a "self" link identifying the location of the quote resource. It also have a "accept-quote" action that instructs the client about how to accept the quote. In this case all the client has to do is to POST a predefined JSON value to http://mythological-shipment.com/api/quotes/myqo-129-gyh/state.

The result of accepting a quote is that it is converted to a sales order (in lack of better domain understanding - there's probably a better word for it). So the accept-quote operation results in a redirect to the newly create sales order which the client can GET:

{
  "id": "myqo-129-gyh",
  "weight": 2.3,
  "volume: 4,
  "origin": "CPH",
  "destination": "AMS":
  "price": 12,
  "@links":
  {
    "self":
    {
      "href": "http://mythological-shipment.com/api/orders/myqo-129-gyh"
    },
    "myth:quote":
    {
      "href": "http://mythological-shipment.com/api/quotes/myqo-129-gyh"
    },
    "myth:shipment-label":
    {
      "href": "http://mythological-shipment.com/api/quotes/myqo-129-gyh/label",
      "type": "application/pdf"
    }
  }
}


At last the customer needs a shipment label to print out and stick onto the package. All it has to do is to follow the "myth:shipment-label" link and GET the PDF. Thats it.

/Jørn