Using hypermedia to build an offline-first webapp
Joost Cassee
joost@cassee.net
@jcassee
jcassee.com
Nextbuild, 30 May 2015
Press down / right to advance slides
Human Environment and Transport Inspectorate
"Monitor and encourage compliance with legislation [for safe] human environment and transport."
Using hypermedia to build an offline-first webapp
REST as it should be™
An extremely personal view on REST
Resource
URI
http://example.com/ship/9334026
"An entity"
Representation
{
"name": "VOS PROMINENCE",
"imo": 9334026,
"type": "cargo"
}
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
POST
(PATCH
Retreive a representation
Store a representation
Delete a resource
"Process" a representation
Update a resource)
}
idempotent
Sending
{
"name": "VOS PROMINENCE",
"imo": 9334026,
"type": "cargo"
}
results in response
GET http://example.com/ship/9334026
Accept: application/json
PUT http://example.com/ship/9334026
Content-Type: application/json
{
"name": "COSCO BREMERHAVEN",
"imo": 9334026,
"type": "cargo"
}
results in changing the name of the ship
Sending
REST is CRUD
... for the most part
Expose business logic as state manipulation
Linking
Relation
http://example.com/relations/owner
"It 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
HAL
Hypermedia Application Language
application/hal+json
{
"name": "VOS PROMINENCE",
"imo": 9334026,
"type": "cargo",
...
"_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"
}
}
}
}
}
Links vs. properties
{
"_links" : {
"http://example.com/relations/profile-image" : {
"href" : "http://example.com/user/23/profile-image"
}
}
}
{
"profileImageUri": "http://example.com/images/bunny.jpg"
}
Backward-compatible 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"
}
}
}
Using hypermedia to build an offline-first webapp
Operate on local data, keep it synchronized
Browser Cache?
Application Cache
for pages, code and assets
index.html
<html manifest="offline.appcache">
offline.manifest
CACHE MANIFEST
# v1 2015-05-30
index.html
styles/main.css
images/logo.png
scripts/app.js
NETWORK:
*
IndexedDB
for data
Using IndexedDB
var db;
var request = indexedDB.open("MyDatabase");
request.onsuccess = function(event) {
db = event.target.result;
};
var transaction = db.transaction("customers");
var objectStore = transaction.objectStore("customers");
objectStore.openCursor().onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
console.log(cursor.key + ": " + cursor.value.name);
cursor.continue();
}
};
resources:
uri -> representation
dirty:
uri
resourcecache:
Itempotent flows
GET
- HTTP GET request
- If succes: cache put
If failure: cache get
PUT
- Cache put dirty
- HTTP PUT request
- If success: clear dirty
If failure: leave dirty
DELETE flow similar to PUT
Non-idempotent flow
POST
- Cache request
- HTTP POST request
- If success: remove request
If failure: leave request
When coming online
PUT all dirty resources
Reissue all stored POST requests in order
Pruning the cache
- Mark live resources
- Remove non-reachable resources
Server push
Keep related resources synchronized
Deleting an item changes the collection
STOMP
https://github.com/jmesnil/stomp-websocket
Simple pub/sub over websockets
Subscribe to resource updates
SUBSCRIBE
id:0
destination:http://example.com/company/3342/ships
MESSAGE
subscription:0
message-id:007
destination:http://example.com/company/3342/ships
content-type:application/hal+json
{
"total": 5,
...
Summary
- Model business logic as state manipulation
- Manipulate state locally
- Synchronize state using HTTP and STOMP
- Use IndexedDB for storing local state
- Use Application Cache for offline operation
- Use hypermedia for API evolution
Code will be available at https://github.com/jcassee
Photo credits
http://en.wikipedia.org/wiki/File:FCC_and_CE.jpg
https://www.flickr.com/photos/tranbc/6944672554
http://commons.wikimedia.org/wiki/File:Air_Malta_Pre_Flight_Inspection_Airbus_A320.jpg
http://commons.wikimedia.org/wiki/File:Fireworks_PDX_1.jpg
http://commons.wikimedia.org/wiki/File:Nationaal_Park_De_Biesbosch.jpg
http://jakarta.usembassy.gov/pr_09222010.html
http://commons.wikimedia.org/wiki/File:Een_%28oud%29_voorbeeld_van_een_vergunnning_2013-05-09_21-53.jpeg
http://commons.wikimedia.org/wiki/File:TsingTao_Express_IMO_9320702,_Port_of_Rotterdam,_Holland,_06JAN2009_pic1.JPG
http://commons.wikimedia.org/wiki/File:Pet_dog_fetching_sticks_in_Wales-3April2010.jpg
Using hypermedia to build an offline-first webapp
By Joost Cassee
Using hypermedia to build an offline-first webapp
For the Dutch Inspectie Leefomgeving en Transport I built a webapp that allow inspectors to work without an internet connection, e.g. when inside a ship. The result is a reusable library for offline apps based on Hypermedia APIs (HAL).
- 1,630