Hypermedia
and API evolution

Joost Cassee

joost@cassee.net

@jcassee

jcassee.com

Dutch API Community Meetup #2
8 June 2016

Painless Software

I will talk about

  • Backward compatibility in APIs
     
  • Hypermedia with HAL
     
  • How hypermedia facilitates API evolution
GET /page2 HTTP/1.1
Host: example.com
Referer: http://example.com/page1

?

Internal code

External code

APIs

2.7.6

Oh crap!

Shiny!

Oops!

Big design up front

Long release cycle

Do not break backward compatibility!

SalesForce

 

Twilio

 

Facebook

 

 

GitHub

 

Azure

/services/data/v20.0/sobjects/Account

 

/2010-04-01/Account/

 

/me?v=1.0  →  /v2.0/me

 

 

Accept: application/vnd.github.v3+json

 

X-MS-Version: 2015-04-05

Resource

URI

http://example.com/ship/9334026

"An entity"

Representation

{
  "name": "VOS PROMINENCE",
  "imo": 9334026,
  "type": "supply"
}

Media Type

application/json

Type

Profile
http://example.com/profiles/ship

"It is a ship"

The resource identified by http://example.com/ship/9334026

{
  "name": "VOS PROMINENCE",
  "imo": 9334026,
  "type": "cargo"
}

and is represented as application/json by

has profile http://example.com/profiles/ship

Representational State

... Transfer

A resource identified by a http/https URL

can be manipulated using HTTP methods

GET

PUT

DELETE

PATCH

POST

Retreive a representation

Store a representation

Delete a resource

Update a resource

"Process" a representation

REST is CRUD

Model business logic as state manipulation

P

Hypertext reference

href

http://example.com/company/3342

"That entity over there"

References in resource state

{
  "name": "VOS PROMINENCE",
  "imo": 9334026,
  "type": "supply",
  "ownerHref": "http://example.com/company/3342"
}

"That is the owner"

(semantics described by the profile)

Linking

Relation
http://example.com/relations/owner

"The entity has an owner"

The resource identified by http://example.com/ship/9334026

to the resource identified by http://example.com/company/3342

has relation http://example.com/relations/owner

(Looks a bit like an RDF triple)

HAL

Hypermedia Application Language

application/hal+json

{
  "name": "VOS PROMINENCE",
  "imo": 9334026,
  "type": "supply",
  ...
{
  "name": "VOS PROMINENCE",
  "imo": 9334026,
  "type": "supply",
  "_links": {
    "self": {
      "href": "http://example.com/ship/9334026"
    },
    "profile": {
      "href": "http://example.com/profiles/ship"
    },
    "http://example.com/relations/owner": {
      "href": "http://example.com/company/3342"
    }
  },
  ...
{
  ...
  "_embedded": {
    "http://example.com/relations/owner": {
      "name": "Vroon B.V.",
      "_links": {
        "self": {
          "href": "http://example.com/company/3342"
        },
        "profile": {
          "href": "http://example.com/profiles/company"
        }
      }
    }
  }
}

HATEOAS

Hypertext As The Engine Of Application State

"Use links to traverse the API"

Elements of a hypermedia API

  1. One or more media types
     

  2. A set of relations
     

  3. A set of profiles
     

  4. The URI of the root resource

It's a protocol!

Hypermedia clients ...

  • know about media types, profiles, relations and root URI

  • treat URIs as opaque

  • let links drive interactions

Rule 1

 

Clients ignore unknown properties

... which means

  • Servers may return more properties in GET response







     
  • Clients may return fewer properties in PUT request
{
  "name": "VOS PROMINENCE",
  "imo": 9334026,
  "type": "supply"
}
{
  "name": "VOS PROMINENCE",
  "imo": 9334026,
  "type": "supply",
  "flag": "nl"
}

Partial PUTs

Missing properties are set to the previous value

or
the default value

Removing or changing the meaning of properties

 

is not allowed

Rule 2

 

Links and embedded resources are metadata

... which means

GET

application/hal+json

1.

{
  "name": "VOS PROMINENCE",
  "imo": 9334026,
  "type": "supply",
  "_links": {
    "self": {
      "href": "..."
    },
    ...
}

application/json

PUT

Links can be added and removed

2.

{
  "name": "VOS PROMINENCE",
  "_links": {
    "owner": {
      "href": "..."
    },
    "prev-owner": {
      "href": "..."
    }
}
{
  "name": "VOS PROMINENCE",
  "_links": {
    "owner": {
      "href": "..."
    },
    "builder" {
      "href": "..."
    }
}

State reference

  • Part of resource state

  • Defined by profile

  • Mutable by client

  • Removal is backward-incompatible

Link relation

  • Part of API wiring

  • Defined by relation

  • Not mutable by client

  • Can be removed

vs.

{
  "_links": {
    "rel": { "href": "uri" }
  }
}
{
  "property": "uri"
}

On the client, log the use of deprecated relations

Link deprecation

{
  "name": "VOS PROMINENCE",
  "_links": {
    "http://example.com/relations/prev-owner": {
      "href": "http://example.com/company/177",
      "deprecation": "http://example.com/docs/deprecated/prev-owner.html"
    }
}

Using OAuth?

  • Ask for client credentials
     
  • Record clients in request log
     
  • Contact developers who use deprecated resources

Extension

{
  "name": "VOS PROMINENCE",
  "imo": 9334026,
  "_links": {
    "self": {
      "href": "..."
    },
    "owner": {
      "href": "..."
    }
}
{
  "name": "VOS PROMINENCE",
  "imo": 9334026,
  "flag": "nl",
  "_links": {
    "self": {
      "href": "..."
    },
    "owner": {
      "href": "..."
    },
    "builder": {
      "href": "..."
    }
}

Topology change

{
"_links": {
  "http://example.com/relations/owner": {
    "href": "http://example.com/company/3342"
  }
}
{
"_links": {
  "http://example.com/relations/owner": {
    "href": "http://companies.example.com/3342"
  }
}

Concept change

{
  "_links" : {
    "http://example.com/relations/owner" : {
      "href" : "http://example.com/company/3342",
      "deprecation": "http://example.com/docs/deprecated/owner.html"
    },
    "http://example.com/relations/ownership" : {
      "href" : "http://example.com/ship/9334026/ownership/3"
    }
  }
}

Behavior Testing

Feature:

Scenario:

Getting information about companies that own ships

Get a ship's owner

Given

And

When

And

Then

a company named "Joost Shipping"

ship named "Providence" owned by "Joost Shipping"

you get the ship "Providence"

you follow the relation "ship-owner"

the response contains

{

  "name": "Joost Shipping"

}

I talked about

  • Backward compatibility in APIs
     
  • Hypermedia with HAL
     
  • How hypermedia facilitates API evolution

Thanks for listening!

joost@cassee.net

@jcassee

jcassee.com

Slides can be found at

https://cass.ee/dutchapi

Check out the example project at
https://github.com/jcassee/registronavale

Hypermedia and API evolution

By Joost Cassee

Hypermedia and API evolution

Presented at the Dutch API Community Meetup #2 on 8 June 2016

  • 2,094