class: center, middle, inverse # tl;dr Intro to the OADA API ## Flavored with Trellis ----------------------------------- [http://trellisframework.org](http://trellisframework.org) [http://github.com/oada](http://github.com/oada) [http://github.com/trellisfw](http://github.com/trellisfw) .footnote[ created with [remark](http://github.com/gnab/remark) ] --- class: center, middle, inverse # Brief intro to API's --- class: middle, inverse # Problem: you have bytes about your dog Fido. * Steve wants those bytes. * You want Steve to have those bytes. --- class: middle, inverse # Problem: you have some bytes about your dog Fido. * Steve wants those bytes. * You want Steve to have those bytes. ## Solution: you email the bytes to Steve. --- class: middle, inverse # Problem: Steve loses emails. * You are on vacation today: Steve needs to get them himself. --- class: inverse # Problem: Steve loses emails. * You are on vacation today: Steve needs to get them himself. ## Solution: Webpage [https://pets.com/fido](https://pets.com/fido) * give link to Steve * always has latest info about Fido * bytes returned have HTML format Request: ```http GET /fido HTTP/1.1 Host: pets.com ``` Response: ```xml
Fido
Nickname: fifi ``` --- class: center, middle, inverse # You have an API! Host: `pets.com` Security: SSL, public Endpoint|Content-Type| Description :------:|:----------:|:--------------: `/fido` | `text/html`| Info about Fido --- class: inverse # Problem: Fido is female and pregnant. --- class: inverse # Problem: Fido is female and pregnant. ## Solution: add more endpoints ------------------------------------------- Endpoint |Content-Type| Description :---------|:----------:|:-------------- `/fido` | `text/html`| Info about Fido `/frida` | `text/html`| Info about Frida `/fritter` | `text/html`| Info about Fritter `/fila` | `text/html`| Info about Fila `/flipper` | `text/html`| Info about Flipper `/felix` | `text/html`| Info about Felix --- class: inverse # Problem: Steve can't remember all your dogs --- class: inverse # Problem: Steve can't remember all your dogs ## Solution: Add a list document ----------------------------------- Endpoint |Content-Type| Description :---------|:----------:|:-------------- `/dogs` | `text/html`| Links to all dogs `/dogs/fido` | `text/html`| Info about Fido `/dogs/frida` | `text/html`| Info about Frida `/dogs/fritter` | `text/html`| Info about Fritter `/dogs/fila` | `text/html`| Info about Fila `/dogs/flipper` | `text/html`| Info about Flipper `/dogs/felix` | `text/html`| Info about Felix --- class: inverse # Problem: Steve forgets. Alot. * Steve watches lots of dogs, including yours. --- class: inverse # Problem: Steve forgets. Alot. * Steve watches lots of dogs, including yours. ## Solution: Custom app to read dog feeding schedule -------------------------------------- Endpoint |Content-Type| Description :---------|:----------:|:-------------- `/api/dogs` | `application/json`| Links to all dogs `/api/dogs/{id}` | `application/json`| Info about dog {id} `/api/dogs/{id}/schedule` | `application/json`| {id}'s feeding schedule --- class: inverse # Example request from an app: List Request: ```http GET /api/dogs HTTP/1.1 Host: pets.com ``` Response: ```javascript HTTP/1.1 200 OK Content-Type: application/json { 'fido': 'https://pets.com/api/dogs/fido', 'frida': 'https://pets.com/api/dogs/frida', 'fritter': 'https://pets.com/api/dogs/fritter', 'fila': 'https://pets.com/api/dogs/fila', 'flipper': 'https://pets.com/api/dogs/flipper', 'felix': 'https://pets.com/api/dogs/felix', } ``` Code: ```javascript const res = await request .get('https://pets.com/api/dogs/fido/schedule'); const all_dogs = JSON.parse(res.body); console.log('Here are all the dogs: ', all_dogs); ``` --- class: inverse # Example request from an app: Fido's feeding schedule Request: ```http GET /api/dogs/fido/schedule HTTP/1.1 Host: pets.com ``` Response: ```javascript HTTP/1.1 200 OK Content-Type: application/json { feedings: [ { 'time': 144799203, 'amount': 2, 'units': 'scoop', 'done': true }, { 'time': 144799777, 'amount': 2, 'units': 'scoop', 'done': false } ], } ``` --- class: inverse # Example request from an app: Fido's feeding schedule ## Alert Steve about Fido: Code: ```javascript const response = await request.get('https://pets.com/api/dogs/fido/schedule'); const fido_schedule = JSON.parse(response.body); const now = moment(); fido_schedule.feedings.each(feeding => { if (now.isAfter(moment(feeding)) && !feeding.done) { alert_steve('Fido needs food!!'); } }); ``` --- class: middle, inverse # Problem: Fido's info should be private --- class: middle, inverse # Problem: Fido's info should be private ## Solution: Add an authorization token Request: ```http GET /api/dogs/fido/schedule HTTP/1.1 Host: pets.com Authorization: Bearer 20kdjf20i3kjlejf03i ``` * OAuth2 is how you hand out tokens via Trellis. * Can do client grant flow, implicit flow, code flow, device flow --- class: middle, inverse # Problem: Steve can't remember his password... --- class: middle, inverse # Problem: Steve can't remember his password... ## Solution: Single Sign-On * "Login with Google" * Trellis does this via OpenIDConnect + oauth-dyn-reg * Any trusted login can be used anywhere --- class: center, middle, inverse # Trellis is a REST API spec + formats ## So it's worth reviewing some things about REST API's... --- class: inverse # Important Stuff About REST: ## Principle 1: Everything is a _resource_. * resource is a generic concept for "some bytes" --- class: inverse # Important Stuff About REST: ## Principle 1: Everything is a _resource_. ## Principle 2: Resources have a unique ID * i.e. a URL `/dogs/fido/schedule` --- class: inverse # Important Stuff About REST: ## Principle 1: Everything is a _resource_. ## Principle 2: Resources have a unique ID ## Principle 3: Resources should be _cacheable_ via a _version_ * GET `/dogs/fido/schedule` at version `3` --- class: inverse # Important Stuff About REST: ## Principle 1: Everything is a _resource_. ## Principle 2: Resources have a unique ID ## Principle 3: Resources should be _cacheable_ via a _version_ ## Principle 4: HATEOAS: HAs To bE the wOrst Acronym eveR * Hypertext As The Engine Of Application State * Means resources should be able to _link to each other_ * GET `/dogs` => `{ fido: { _id: "resources/123" } }` * GET `/dogs/fido` => `{ schedule: 'Twice daily' }` * GET `/dogs/fido/schedule` => `'Twice daily'` --- class: inverse # Important Stuff About REST: ## Principle 1: Everything is a _resource_. ## Principle 2: Resources have a unique ID ## Principle 3: Resources should be _cacheable_ via a _version_ ## Principle 4: HATEOAS ## Principle 5: Resources have _content-types_ * describes the format or data model for the resource * looks like `application/vnd.trellis.audit.globalgap.1+json` --- class: inverse # Important Stuff About REST: ## Principle 1: Everything is a _resource_. ## Principle 2: Resources have a unique ID ## Principle 3: Resources should be _cacheable_ via a _version_ ## Principle 4: HATEOAS ## Principle 5: Resources have _content-types_ ## Principle 6: Only 4 verbs * GET, PUT, POST, DELETE * all else is nouns (i.e. state) --- class: center, middle, inverse # OADA Design Goals ## based on REST architectural principles --- class: center, middle, inverse # Goal: Only need to know domain --- class: middle, inverse # Goal: Only need to know domain ## Solution: /.well-known/ Discover domain stuff here. `/.well-known/oada-configuration`: ```javascript { "oada_base_uri": "https://api.example.org/v1", "authorization_endpoint": "https://example.org/auth", "token_endpoint": "https://example.org/token", "registration_endpoint": "https://example.org/register", "token_endpoint_auth_signing_alg_values_supported": [ "RS256" ] } ``` similar for openid-configuration [//]: # (-------------------------------------------------------------) --- class: center, middle, inverse # Goal: Easy to adapt well-designed existing REST API's --- class: middle, inverse # Goal: Easy to adapt well-designed existing REST API's ## Example: Trello API
[https://developers.trello.com/v1.0/reference](https://developers.trello.com/v1.0/reference) ... hmmm, looks like a file system ... ... which is just a graph ... [//]: # (-------------------------------------------------------------) --- class: middle, inverse # Goal: Easy to adapt well-designed existing REST API's ## Solution: URL's are really JSON graphs of resources * `/a/b` --> `{ a: { b: 42 } }` * `/a` --> `{ b: { _id: 'resources/125' } }` * define a `content-type` for each resource level of the graph [//]: # (-------------------------------------------------------------) --- class: center, middle, inverse # /resources --- class: middle, inverse # /resources * GET: give me the resource * PUT: idempotent* merge with existing resource, or create new one * POST: create a random id, then do a PUT there * DELETE: ummm.....delete ## --> JSON types: * \+GET/PUT/POST/DELETE at any level * \+URL's implicitly follow links (covered later) \* idempotent jokes are funny no matter how often you tell them --- class: middle, inverse # /resources ```http GET /resources/123 HTTP/1.1 { "_id": "resources/123", "_rev": "4", "_type": "application/vnd.montypython.grail.1+json", "_meta": { "_id": "resources/123_meta", "_rev": "3" }, "the_knights": "who say Ni!" } ``` `_id` is part of URL after the OADA base --- class: middle, inverse # /resources ## Actual resource: ```http GET /resources/123 HTTP/1.1 { "_id": "resources/123", "_rev": "4", "_type": "application/vnd.montypython.grail.1+json", "_meta": { "_id": "resources/123/_meta", "_rev": "3" }, "the_knights": "who say Ni!" } ``` ## Versioned link to resource: ```http { "_id": "resources/123", "_rev": "4" } ``` Link looks same as resource, just "unexpanded". --- class: center, middle, inverse # Goal: find the resources you want --- class: middle, inverse # Goal: find the resources you want ## Solution: `/bookmarks` resource discovery ```http GET /bookmarks HTTP/1.1 { "_id": "123", "_rev": "6-k6i67i", "_type": "application/vnd.oada.bookmarks.1+json". "_meta": { "_id": "resources/123/_meta", "4-kdfj02fkd" }, "trellisfw" : { "_id": "resources/0ijo2flk", "3-kdjf02fj2" }, } ``` * it's a resource * each user has their own: `/bookmarks` === `/users/me/bookmarks` --- class: center, middle, inverse # Goal: agnostic to ontology and format --- class: middle, inverse # Goal: agnostic to ontology and format ## Solution: _type * media type of /bookmarks and any other resources determine available graph structure * Duck typing! --- class: center, middle, inverse # Goal: Efficient sync --- class: middle, inverse # Goal: Efficient Sync ## Solution Part 1: `_changes` as in-order stream of idempotent merges ```javascript GET /resources/1kdi3/_meta/_changes/5 { body: { a: { b: 'hello', }, } } ``` * Each resource is like its own Kafka topic * GET's can request changes since a version instead of the resource itself * PUT bodies send back the change document * Coming soon: reverse-changes! --- class: middle, inverse # Goal: Efficient Sync ## Solution Part 2: versioned links * versioned links contain _rev as part of parent document: ```http GET /bookmarks/trellis/certifications HTTP/1.1 { "_id": "resources/123", "_rev": "4", "_type": "application/vnd.trellis.certifications.1+json", "_meta": { "_id": "resources/123/_meta", "_rev": "3" }, "111": { "_id": "resources/200", "_rev": "2" }, "222": { "_id": "resources/201", "_rev": "1" }, "333": { "_id": "resources/202", "_rev": "6" } } ``` * ...so parent changes shortly after child changes...and so on... * can efficiently poll for changes as simplest efficient case --- class: middle, inverse # Goal: Efficient Sync ## Solution Part 3: Webhooks and Websockets * Register for change notifications for any versioned sub-tree * i.e. "tell me when we get a change to the certifications list" * webhooks: * server - to - server * websockets: * browser/app - to - server * `watch` sends stream of change documents for subtree * can "restart" stream from any `_rev` => at-least-once delivery --- class: middle, inverse # Goal: Efficient Sync ## Solution Part 4: OADA Intelligent Sync * Hey, we have a standard API... * "When this one changes, replay/merge those changes over here" * Basically a webhook that knows how to do the OADA PUT --- class: center, middle, inverse # Goal: Concurrency --- class: middle, inverse # Goal: Concurrency ## Solution: Optimistic Locking * "Fail this write if `5` is no longer the current `_rev`" * VERY useful for safely building trees from browsers....(see `oada-cache`) * Requires server to actually know latest `_rev`... * If data model is CRDT, no merge conflicts! --- class: middle, inverse # Goal: Multiple formats, copies, derivatives, reactions, ... ## Solution: Microservices! * OADA turns out to be a framework for microservices --- class: middle, inverse # Goal: Use OADA to drive the browser ## Solution: oada-cache * [https://github.com/oada/oada-cache](https://github.com/oada/oada-cache) * transparent cache in browser with real-time updates over websockets * Coming soon: native, transparent, offline-first! --- class: middel, inverse # Goal: public formats ## Solution: `oada-formats` Defined by [https://github.com/oada/oada-formats](https://github.com/oada/oada-formats) Trellis thus far: [https://github.com/OADA/oada-formats/tree/master/formats/application/vnd](https://github.com/OADA/oada-formats/tree/master/formats/application/vnd) Basics are: * every format has an example * every format has at least a javascript `validate()` function * every JSON format can just supply a json-schema * extensions in the library to simplify indexing, traveling context, duck typing, vocabularies, and more! --- class: middle, inverse # Example: Exploring Trellis --- class: middle, inverse # Example: Exploring Trellis ## GET list of certifications for this token: ```http GET /bookmarks/trellisfw/certifications HTTP/1.1 { "_id": "resources/c8880a56-2761-4e9f-8abe-4fbba321fc6b", "_rev": "38", "_type": "application/vnd.trellisfw.certifications.1+json", "_meta": { "_id": "resources/c8880a56-2761-4e9f-8abe-4fbba321fc6b/_meta", "_rev": "38" }, "114a9322-1a87-4190-8d9c-a9d3c064119e": { "_id": "resources/4bda9c16-de80-4a73-b586-d58d84862dbe", "_rev": "37", }, "fc7f5bfe-1fab-40d4-9ea5-62a295697f09": { "_id": "resources/7a09a14e-c11b-41b8-99c0-98d45c5755a5", "_rev": "31", }, } ``` --- class: middle, inverse # Example: Exploring Trellis ## GET information for one certification * Note that a "certification" is a combination of audit, corrective actions, and certificate * This one just has audit (note duck typing): ```http GET /bookmarks/trellisfw/certifications HTTP/1.1 { "_id": "resources/4bda9c16-de80-4a73-b586-d58d84862dbe", "_rev": "37", "_meta": { "_id": "resources/4bda9c16-de80-4a73-b586-d58d84862dbe/_meta", "_rev": "37", }, "_type": "application/vnd.trellisfw.certification.globalgap.1+json", "audit": { "_id": "resources/2be4b567-667b-4f4a-b21d-8602e4c43d85", "_rev": "31", } } ``` --- class: middle, inverse # Example: Exploring Trellis ## Create a new certification 1. Create a new resource with audit as body `POST /resources` -> returns _id 2. Create a new certification resource that links to the new audit: ```http POST /resources { _type: "application/vnd.trellis.certification.1+json", audit: { _id: "resources/12345jfd", _rev: "0" } } ``` --- class: middle, inverse # Example: Exploring Trellis ## Create a new certification 1. Create a new resource with audit as body `POST /resources` -> returns _id 2. Create a new certification resource that links to the new audit: 3. Link the new resource under the user's list of certifications (example using PUT with client-generated uuid): ```http PUT /bookmarks/certifications { "kd0f2i3j3": { _id: "resources/98765kjdfk", _rev: "0" } } ``` This merges the new uuid key in with the rest. --- class: middle, inverse # Now you can try it yourself! ``` git clone git@github.com:OADA/oada-srvc-docker.git cd oada-srvc-docker docker-compose up -d ``` [https://github.com/oada/oada-srvc-docker](https://github.com/oada/oada-srvc-docker) --- class: middle, inverse # Fun with Websockets Do this in Chrome dev tools console: ```javascript var w = new WebSocket('wss://localhost'); w.onopen = function(evt) { console.log('open!!') }; w.onerror = function(evt) { console.log('ERROR! evt = ', evt); } w.onmessage = function(evt) { console.log('message! ', JSON.parse(evt.data)) }; w.send(JSON.stringify({ requestId: '1', method: 'watch', headers: { authorization: 'Bearer aaa' }, path: '/bookmarks', })); ``` Then go to execute a PUT against bookmarks and see the change stream in! * You can send `x-oada-rev` with the watch to stream from last known rev * Changes to versioned-link children will _bubble up_: the parent's change document contains the child's data directly