On Github zmeda / mc-hateoas-evaluation
[H]ypermedia [a]s [t]he [E]ngine [o]f [A]pplication [S]tate
Standard/Specification on how servers and clients interact
Initial user feedback
both human and machines understand and know how to follow links. use OPTIONS and GET to explore URL and understand the relationships better.
the entire application (all the APIs) look and behave in similar ways
its not about creating a application, its about a meta-application - the web. its about integration with other applications/platforms
documentation won't be for a super smart client that will automatically understand and navigate the application. Its to ease up the clients developers life when integrating or using our APIIts obviously more complex to build HATEOAS applications (servers)
Its something that does not come naturally to all developers. There's a learning curve to understand how to effectively use HATEOAS. It may lead to a crAPI.
Some relationships won't be so common sense as the article of an order. These relationships will still need some proper documentation to be understanded and used effectively.
Known hypermedia specifications:
Purpose:
Last update: 2015-05-29
Extendable
Get a specific order
GET /orders/1 HTTP/1.1 Accept: application/vnd.api+json
{ "data": null, "errors": null, "meta": null, "links": null, "included": null, "jsonapi": null }Every document should have one of "data", "errors" or "meta". The others are optional. "DATA" - where the actual data is provided "ERRORS" - in case some error happened "META" - for non-standard meta information "LINKS" - links for the main data represented "INCLUDED" - in case the client requests embeded data "JSONAPI" - description of server's implementation
{ "data": { "id": "1", "type": "order", "links": { "self": "/orders/1" }, "attributes": "...", "relationships": "..." }// ... }A resource object must always contain its type and ID. The properties of the resource go inside the attributes object May contain links for the resource or related resources.
{ "data": { "id": "1", "type": "order", "links": "...", "attributes": { "order-id": "1", "order-number": "1010101010", "shipment-number": "1012121212", "status": "approved" }, "relationships": "..." }// ... }
{ "data": { "id": "1", "type": "order", "links": "..." }, "attributes": "...", "relationships": { "order-items": { "data": [ {"id": 1, "type": "order-item"}, {"id": 2, "type": "order-item"} ], "links": { "related": "/orders/1/items" } } } }
{ "data": { "id": "1", "type": "order", "links": "...", "attributes": "...", "relationships": "..." }, "meta": { "order-items-count": 2, "order-lines-count": 3, "full-order-price": "1000" } }
Siren is a hypermedia specification for representing entities.
Last update: 2015-10-21
Entity consits of:
Get a specific order
GET /orders/1 HTTP/1.1 Response: application/vnd.siren+json
Define entitiy's purpose.
{ "class": [ "order" ], "title": "", "properties": {}, "entities": [], "links": [], "actions": [] }
Descriptive text about the entity.
{ "class": [], "title": "An order entity which represents the top level order.", "properties": {}, "entities": [], "links": [], "actions": [] }
A set of key-value pairs describing the state of the entity.
{ "class": [], "title": "", "properties": { "order-id": "1", "order-number": "1010101010", "shipment-number": "1012121212" }, "entities": [], "links": [], "actions": [] }
A collection of related sub entities. Important part is the relation between the parent and a child entity.
{ "class": [], "title": "", "properties": {}, "entities": [ { "class": [ "items", "collection" ], "rel": [ "/rels/order-items" ], "href": "/orders/1/items" } ], "links": [], "actions": [] }Sub entity could be either embedded link or embedded representation.
Non-entity based relation bound to the entity.
{ "class": [], "title": "", "properties": {}, "entities": [], "links": [ { "rel": [ "self" ], "href": "/orders/1/" } ], "actions": [] }Difference between embedded entities and links. Links are used for linking something outside of application domain (in our case article). Embedded entities are used normally within the same context (in our case order, order-line, order-item).
A collection of actions that can change the state of the entity.
{ "class": [], "title": "", "properties": {}, "entities": [], "links": [], "actions": [ { "name": "update-order-status", "title": "Update an order status", "method": "POST", "href": "/orders/1", "type": "application/json", "fields": [ { "name": "status", "type": "radio", "value": "approved" } ] } ] }With the use of "radio" button we can easily manipulate state transitions.
Hypertext Application Language
Last update: 2013-09-18
Get a specific order
GET /orders/1 HTTP/1.1 Accept: application/hal+json
{ "_links": { "self": { "href": "/orders/1" } }, "_embedded": {}, "order-id": "1", "order-number": "1010101010", "shipment-number": "1012121212" }
{ "_links": {}, "_embedded": { "items": [{ "_links": { "self": { "href": "/orders/1/items/1"}}, "order_item_id": "1" }] }, "order-id": "1", "order-number": "1010101010", "shipment-number": "1012121212" }
{ "meta": { "count": 3 }, "data": [ { "type": "order", "id": "1", "attributes": { "order-number": "1010121312379", "shipment-number": "1010123123", "status": "approved" } }, { "type": "order", "id": "2", "attributes": { "order-number": "1010121312380", "shipment-number": "1010123124", "status": "approved" } }, { "type": "order", "id": "3", "attributes": { "order-number": "1010121312381", "shipment-number": "1010123123", "status": "initial" } } ], "links": { "self": "/orders?page[cursor]=XYZ0", "first": "/orders?page[cursor]=XYZ0", "prev": null, "next": "/orders?page[cursor]=XYZ1", "last": "/orders?page[cursor]=XYZN" } }
{ "class": [ "orders", "collection" ], "title": "A list of orders", "properties": {}, "entities": [], "links": [ { "rel": [ "self" ], "href": "/orders/1/" }, { "rel": [ "first" ], "href": "/orders?page[cursor]=XYZ0" }, { "rel": [ "next" ], "href": "/orders?page[cursor]=XYZ1" }, { "rel": [ "last" ], "href": "/orders?page[cursor]=XYZN" }, ], "actions": [] }
{ "_links": { "self": { "href": "/orders/1/" }, "first": { "href": "/orders?page[cursor]=XYZ0" }, "next": { "href": "/orders?page[cursor]=XYZ1" }, "last": { "href": "/orders?page[cursor]=XYZN" }, }, "_embedded": [] }
Sorting is not directly a part of HATEOAS.
Best practice:
GET /orders?sort=-created_at,status HTTP/1.1
Filtering is not directly a part of HATEOAS.
Best practice:
GET /orders?status=initial HTTP/1.1
Team decision / Open discussion