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
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
-
One or more media types
-
A set of relations
-
A set of profiles
-
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
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,187