Love to HATEOAS

Hypermedia​ for Fun and Profit  →

Gabe Sullice

 

Senior Engineer - Acquia Octo

 

JSON API co-maintainer

HTTP/2 enthusiast

Specification-junkie

Loses lots of sleep over API design

HTTP

It's the Hypertext Transfer Protocol

and that's a protocol layer sitting atop the TCP/IP network protocols

And there are many such protocols.

SMTP, POP & IMAP

SSH

FTP

IRC

& XMPP

SOAP

HTTP IS AN APPLICATION Language

Understanding HTTP

HTTP is an application protocol for distributed, collaborative, and hypermedia information systems.

URis, METHODS, and messages 

  • Resource - A unit of information
  • URI - Uniform Resource Identifier
  • Method - GET, POST, PATCH, DELETE & others
  • Status - 200, 300, 400, 500 inter alia
  • Messages - "Requests" and "Responses"

URI

UNiform resource Identifier

Almost always a URL

Which is simply a unique identifier that can be "located"

http://example.com/api/user/1

 

"http:" "//" host [ ":" port ] [ abs_path [ "?" query ]]

Methods

GET, POST, PATCH, PUT & DELETE

Describe "actions" on a resource

HEAD & OPTIONS are about information discovery

GET - Show me the identified resource

POST - Add this resource

PATCH - Update the identified resource with this information

PUT - Replace the identified resource with this one

DELETE - Remove the identified resource

HEAD - Is there a resource with this identifier?

OPTIONS - What actions can I take on a resource?

Statuses

200, 300, 400, 500

Categorize communication into conversational buckets

200 - Understood

300 - Maybe you meant X?

400 - Sorry, I don't understand

500 - It's not you, it's me

Messages

requests and responses

Contains a start line (URI + Method/Status), headers & body

Headers describe the resource

Content-Type: text/html

<html>
  <title>Gabe Sullice</title> 
  <body>stout</body>
</html>
Content-Type: application/json

{
  "type": "user",
  "id": 1,
  "attributes": {
    "name": "gabesullice"
  }
}

Request

GET www.example.com/user/1 HTTP/1.1

response

HTTP/1.1 200 OK

 

Content-Type: application/json

{
  "type": "user",
  "id": 1,
  "attributes": {
    "name": "gabe"
  }
}

REST

Representational State transfer

not a protocol

REST is an architectural style that defines a set of constraints and properties based on HTTP.

Bad News

Hypermedia API

Principles

  • Client-Server
  • Stateless
  • Cache
  • Layered System
  • Code-on-Demand
  • Uniform Interface

Client-server

A separation of concerns

Client handles presentation

Server handles data storage

Foundational principle for "decoupled" sites

STateless

Every request/response is independent

Cacheability

A response should contain information to govern its own intermediate storage

 

Its purpose is to eliminate requests/responses cycles or to reduce the time it takes for that cycle to complete (latency)

layered system

The system should be indifferent to proxies, load balancers, CDNs, etc.

COde-on-demand

Optional.

 

Useful when a client does not have the know-how on how to process a resource. It sends a request to a remote server for the code representing that know-how, receives that code, and executes it locally.

Uniform Interface

Entity representations are decoupled from their storage.


Hypermedia is the engine of application state.

HYPERmedia

 Hyper text

I'm a teapot, short and stout

✷Hyper✷text✷

<a href="https://httpstatus.es/218">I'm a teapot</a>

Hypermedia

{
  "type": "joke",
  "id": 1,
  "attributes": {
    "content": "I'm a teapot"
  },
  "links": {
    "explanation": "https://httpstatus.es/218"
  }
}

Hypermedia JUST means media with links

A link?

A link is soo much more than that clickable blue text.

A LINK CAN BE VIEWED AS a statement

   It takes the form:

"{this URI} has a {relation type} resource at {that URI}, which has {target attributes}".

RElation Type

A link relation type identifies the semantics of a link.

 

Example:

<a rel="help" href="http://foo.com/guide">

<a rel="my-cart" href="http://foo.com/cart/4">

Target attribute

Hints information about the linked-to resource.

 

Example:

<a rel="help" hreflang="en, fr, es" href="http://foo.com/guide">

<a rel="my-cart" title="Checkout" href="http://foo.com/cart/4">

Hypermedia ➡ Web

index.html

index.html

hero.jpg

index.html

style.css

hero.jpg

index.html

style.css

url('https://www.example.com/assets/separator.svg');

hero.jpg

index.html

style.css

url('https://www.example.com/assets/hipster-heavy.ttf');

url('https://www.example.com/assets/separator.svg');

hero.jpg

/home

/posts

/posts/2

/posts/1

/about

/home

/posts

/posts/2

/posts/1

/about

/posts

/posts/2

/posts/2/tags

/posts/2/author

/posts/1

/posts

/posts/2

/posts/2/tags

/posts/2/author

/posts/1

/posts/2

/posts/2/author

/posts/2/tags

/tags/2

/tags/1

/api/content

/api/schemas/content

/api/content?page=2

/api/posts/1/comments

/api/content

/api/schemas/content

/api/content?page=2

/api/content?page=3

/api/posts/2/comments

/api/posts/1/comments

/api/content

/api/schemas/content

/api/content?page=2

/api/content?page=3

/api/content?page=4

/api/posts/2/comments

/api/posts/1/comments

Difficult QUESTIONS IN API DESIGN

  • How many pages are there?
  • Am I allowed to delete this resource?
  • Is my shopping cart empty?
  • Can I make a bank withdrawal?
  • What URL structure should I use?
  • How do I use "pretty" URLs in my application?
  • How do I represent variations of single entity?
  • How do I add an item to my cart?

We can drive client-server interaction through Hypermedia

HATEOAS

Hypermedia As The Engine Of Application State

HATEOAS

is like

HYperDrive

for your APIs

Hypermedia can provide

related Resources

Hypermedia can provide

Availale variants

 

Hypermedia can provide

Availale Actions

 

Related REsources

  • Previous and next pages of a collection
  • First and last pages of a collection
  • An entity's author
  • Similar products
  • Contextual resources, like tags or categories

Available Variants

  • Revisions
  • Translations
  • Product variants
    • Small, Medium & Large
    • Red, Green or Blue

Available Actions

  • Add
  • Remove
  • Revise
  • Purchase
  • Pay
  • Relate A to B

Example Time

{
    "data": [...],
    "links": {
        "next": "https://foo.com/collection?page=1",
        "last": "https://foo.com/collection?page=10"
    },
}
{
    "data": [...],
    "links": {
        "self": "https://foo.com/birds",
        "family#0": {
            "href": "https://foo.com/birds/paradisaeidae",
            "meta": {
                "title": "Birds of Paradise"
            }
        },
        "family#1": {
            "href": "https://foo.com/birds/corvidae",
            "meta": {
                "title": "Crows, Ravens & Jays"
            }
        }
    }
}
{
    "data": [...],
    "links": {
        "self": "https://foo.com/shirt/1",
        "product-variant#0": {
            "href": "https://foo.com/shirt/1/a",
            "color": "#0033ff",
            "title": "Marine"
        },
        "product-variant#1": {
            "href": "https://foo.com/shirt/1/b",
            "color": "#ee0000",
            "title": "Ruby"
        }
    }
}
{
    "data": [{
        "type": "post",
        "id": 1,
        "attributes": {...},
        "links": {
            "self": "http://foo.com/post/1?resource_version=id:27",
            "latest-version": "http://foo.com/post/1?resource_version=id:29"
        }
    }, {
        "type": "post",
        "id": 2,
        "attributes": {...},
        "links": {
            "self": "http://foo.com/post/21?resource_version=id:13",
            "latest-version": "http://foo.com/post/2?resource_version=id:13"
        }
    }]
}
{
    "data": [{
        "type": "post",
        "id": 1,
        "attributes": {...},
        "links": {
            "self": "http://foo.com/post/1",
            "operation#0": {
                "href": "http://foo.com/post/1",
                "meta": {
                    title: "Edit",
                    rel: "edit"
                }
            },
            "operation#1": {
                "href": "http://foo.com/post/1",
                "meta": {
                    title: "Delete",
                    rel: "drupal://link-relation/jsonapi/remove"
                }
            }
        }
    }]
}
{
    "data": [{
        "type": "product",
        "id": 1,
        "attributes": {...},
        "links": {
            "self": "http://foo.com/product/1",
            "cta": {
                "href": "http://foo.com/cart/4",
                "meta": {
                    title: "Add to Cart",
                    rel: "drupal://link-relation/jsonapi/relate",
                    template: {
                        "method": "POST",
                        "body": "{\"type\": \"product\", \"id\": 1}"
                    }
                }
            }
        }
    }]
}
{
    "data": [{
        "type": "product",
        "id": 1,
        "attributes": {...},
        "links": {
            "self": "http://foo.com/product/1",
            "cta": {
                "href": "http://foo.com/wishlist/5",
                "meta": {
                    title: "Add to Wishlist",
                    rel: "drupal://link-relation/jsonapi/relate",
                    template: {
                        "method": "POST",
                        "body": "{\"type\": \"product\", \"id\": 1}"
                    }
                }
            }
        }
    }]
}
Made with Slides.com