API management with Taffy and API Blueprint

Post on 15-Feb-2017

223 views 4 download

Transcript of API management with Taffy and API Blueprint

API MANAGEMENT WITH TAFFY AND API BLUEPRINT

KAI KOENIG (@AGENTK)

AGENDA

▸ What is Taffy?

▸ REST in a nutshell

▸ But is it “RESTful ENOUGH”?

▸ Taffy setup and basic flow

▸ Advanced configuration

▸ API Blueprint and other tools

WHAT IS TAFFY?

WHAT IS TAFFY?

TAFFY

▸ TLDR version: Taffy is a framework to build REST-based APIs in CFML

▸ Developed by Adam Tuttle

▸ Currently in version 3.1, incepted around 2011 (MIT license)

▸ Very stable, very powerful

▸ Allows the creation of REST-based APIs in CFML independent of any vendor implementation - works with Lucee and ACF

▸ Can be integrated with other frameworks

WHAT IS TAFFY?

WHY TAFFY?

“But hey, both Lucee and Adobe CF have their own implementation of REST-API handling and it’s SO EASY to use and it has SELF-DOCUMENTATION! That’s great, ey?”

▸ At first glance it might seem like a great idea for getting started. But:

▸ Various issues (created URL structures, JSON handling, clunky syntax)

▸ Significantly lock-in to a vendor’s implementation

▸ Overall developer-unfriendly approaches

WHAT IS TAFFY?

BENEFITS OF TAFFY

▸ This is not a talk about the deficiencies of REST in ACF or Lucee

▸ This is why Taffy is great:

▸ Developer-friendly and easy to get started with

▸ Backwards-compatible to ACF 8

▸ Convention over configuration

▸ Hardly any boilerplate code

REST IN A NUTSHELL

https://www.flickr.com/photos/kikasz/2891113802/

REST IN A NUTSHELL

WHAT IS REST?

▸ REpresentational State Transfer - a method to communicate over a network

▸ Mostly used over HTTP, technically protocol-independent

▸ 2 phases:

▸ Request - noun, verb, MIME type, headers, data

▸ Response - status code, status text, headers, data

REST IN A NUTSHELL

THE REQUEST (I)

▸ Nouns - identifier of the data we want to work with, e.g. /users or /users/232

▸ Main verbs:

▸ POST: creating a new record (unsafe)

▸ PUT: updating existing record (idempotent)

▸ GET: retrieving existing record (safe)

▸ DELETE: deleting record (idempotent)

REST IN A NUTSHELL

THE REQUEST (II)

▸ Less commonly used verbs:

▸ HEAD: similar to GET, but only returns headers (safe)

▸ OPTIONS: used to check which verbs are available (safe)

▸ PATCH: similar to PUT, updating individual properties (idempotent)

▸ MIME type:

▸ Client and API server agree on the data types for exchange, e.g. text/html, application/xml etc

REST IN A NUTSHELL

THE REQUEST (III)

▸ Headers - supply metadata with the request (Accept, Content-Type)

▸ Data - the data sent from the client to the server

▸ Can conceptually be of any format

▸ Most common: JSON

REST IN A NUTSHELL

THE RESPONSE

▸ Status code & Status text:

▸ “200 OK”

▸ “404 Not Found”

▸ “500 Server Error” …

▸ There are a lot more specific status codes, such as 201, 301, 302, 400, 401 etc.

▸ Headers and Data

REST IN A NUTSHELL

WHY DO PEOPLE LIKE AND USE REST?

▸ Works naturally with the HTTP protocol

▸ Other alternatives:

▸ SOAP-XML: very enterprise-y, bloated and complex

▸ XML-RPC: similar to REST, but ignores the power of the verb

RESTFUL ENOUGH?

https://www.flickr.com/photos/kikasz/2891113802/https://xkcd.com/386/

BUT IS IT RESTFUL ENOUGH?

THE REALITY IS MORE COMPLEX

▸ Roy Fielding’s 2000 dissertation about what we today know as REST

▸ Followed up by a series of blog posts from ~2008 about HATEOAS (Hypermedia As The Engine Of Application State)

▸ prescribing additional requirements and behaviours

▸ often considered as ideal, but impractical in a lot of ways

▸ Leads to a lot of discussion what REST is and how one has to use it

BUT IS IT RESTFUL ENOUGH?

MORE MATURE?

▸ Plain JSON

▸ HATEOAS

{ "name" : “iPhone 7" }

{ "name": “iPhone 7", "links": [ { "rel": "self", "href": "http://localhost:8080/product/1" } ] }

BUT IS IT RESTFUL ENOUGH?

HATEOAS{ "content": [ { "price": 499.00, "name": "iPad", "links": [ { "rel": "self", "href": "http://localhost:8080/product/1" } ], "attributes": { "connector": "socket" } }, { "price": 49.00, "name": "Dock", "links": [ { "rel": "self", "href": "http://localhost:8080/product/3" } ], "attributes": { "connector": "plug" } } ], "links": [ { "rel": "product.search", "href": "http://localhost:8080/product/search" } ] }

BUT IS IT RESTFUL ENOUGH?

RICHARDSON MATURITY LEVEL

http://martinfowler.com/articles/richardsonMaturityModel.html

TAFFY SETUP AND BASIC FLOW

https://www.flickr.com/photos/joanna_young/4515399787/

TAFFY SETUP AND BASIC FLOW

INSTALLATION

▸ Download .zip-file from Github or taffy.io

▸ Installation options:

▸ Global mapping /taffy

▸ /taffy physically in webroot

▸ Installation in sub-directory of site (needs application-specific mappings)

TAFFY SETUP AND BASIC FLOW

DRIVEN BY APPLICATION.CFC

▸ Extends taffy.core.api

▸ Needs to call onApplicationStart() and onRequestStart() on the parent CFC

▸ Earlier versions of Taffy had their own Application.cfc events - it was recommended to actually not implement onApplicationStart() etc

▸ That’s gone since Taffy 2.0

component extends="taffy.core.api" { this.name = "TaffyDemo";

function onApplicationStart() { application.productList = [ { "id":1, "name":"iPhone 7" }, { "id":2, "name":"iPhone 7+" }, { "id":3, "name":"iPad Pro 9.7inch" } ];

return super.onApplicationStart();

}

function onRequestStart() { return super.onRequestStart(); }

}

TAFFY SETUP AND BASIC FLOW

CONVENTION OVER CONFIGURATION

▸ Taffy tries to NOT be in your way

▸ Examples:

▸ Default function names for resources are get(), post(), put(), delete()

▸ Looks for resources in a /resources directory

▸ Lots of things are just fine and work out of the box naturally because of sensible conventions

TAFFY SETUP AND BASIC FLOW

MINIMAL LAYOUT

TAFFY SETUP AND BASIC FLOW

RESOURCES (I)

▸ Extend taffy.core.resource

▸ Live in the /resources directory or a /resources mapping

▸ Can be changed - requires dropping the default bean factory

▸ Good practice:

▸ Have a CFC for a “thing” and for a “collection of things” each

▸ My convention: “SomeThing” and “SomeThingCollection”

▸ A resource should at least implement one verb function

TAFFY SETUP AND BASIC FLOW

RESOURCES (II)

▸ taffy_uri defines the public URL of your resource

▸ representationOf() triggers the default serialiser

▸ withStatus() sets the HTTP status of the response (defaults to “200 OK”)

component extends="taffy.core.resource" taffy_uri="/product" { public function get(){

return representationOf(application.productList).withStatus(200); } }

TAFFY SETUP AND BASIC FLOW

RESOURCES (III)

▸ Passing {productId} token into the resource’s URL

▸ Returning 404 if there’s no record found

component extends="taffy.core.resource" taffy_uri="/product/{productId}" { public function get(numeric productId){

for (var product in application.productList) { if (product["id"] == arguments.productId) { return representationOf(product).withStatus(200, "OK"); } }

return noData().withStatus(404, "Not Found"); } }

TAFFY SETUP AND BASIC FLOW

RESOURCES (IV)

▸ If a verb is not implemented in a resource, Taffy will return “405 Not Allowed”

▸ Tokens will automatically become part of the arguments scope

▸ Tokens support regular expressions: /product/{pId:\d{4}} - /product/{pId:[A-Za-z]+}

▸ Query string parameters will ALSO automatically become part of the arguments scope

▸ Use .noData() if the intention is to not return data

▸ Similar to .withStatus(…), it’s possible to chain .withHeaders(…) to the return statement

TAFFY SETUP AND BASIC FLOW

TAFFY DASHBOARD

TAFFY SETUP AND BASIC FLOW

TAFFY DASHBOARD

▸ Dashboard allows to:

▸ execute requests (incl. Query Params, Headers and Basic Auth)

▸ inspect the response

▸ see a limited amount of documentation

▸ Should (usually) be switched off in production

▸ Default: caches API, needs explicit reloads

TAFFY SETUP AND BASIC FLOW

CHANGING DATA - WHERE TO PUT POST?

▸ If the POST method was in Product.cfc - we’d have an issue.

▸ The URL route requires us to use /product/{productId}

▸ POST therefore has to life in ProductCollection.cfc

▸ PUT however (updating existing data) can happily go into Product.cfc

▸ URL matching: /product/search will match before /product/{productId}

TAFFY SETUP AND BASIC FLOW

GOOD PRACTICES FOR PUT AND POST

▸ If you created a record with POST:

▸ return “201 Created” with the created data

▸ potentially return x-inserted-id in the header

▸ If you updated a record with PUT:

▸ return “200 OK” with the updated data

▸ if the record to be updated didn’t exist, create it (see above)

ADVANCED TASKS AND CONFIG

ADVANCED TASKS AND CONFIG

BREAKING AWAY FROM THE CONVENTIONS

▸ Metadata:

▸ Annotate functions to be triggered by a specific verb: taffy_verb=“<VERB>”

▸ Necessary for extended verbs like OPTIONS, HEAD, PATCH. Optional for standard verbs.

▸ There’s more metadata in serialisers (later)

ADVANCED TASKS AND CONFIG

BREAKING AWAY FROM THE CONVENTIONS

▸ Configuration: variables.framework struct in Taffy’s Application.cfc

▸ reloadKey/reloadPassword vs. reloadOnEveryRequest

▸ serializer/deserializer (later)

▸ disableDashboard

▸ jsonp/allowCrossDomain

▸ beanFactory - allows hooking into Coldspring, DI/1 etc…

ADVANCED TASKS AND CONFIG

AUTHENTICATION AND SECURITY

▸ Firstly - what do you want to achieve?

▸ Requiring an API Token?

▸ Fine-grain security?

▸ oAuth

▸ HTTP Basic Auth

▸ SSL

ADVANCED TASKS AND CONFIG

API TOKEN (I)

▸ Best place to start is onTaffyRequest(…)

▸ Return true to continue processing the request

▸ Return a representation object to abort the request

▸ Two common options for passing token:

▸ URI parameters and/or as part of the data

▸ In the header

ADVANCED TASKS AND CONFIG

API TOKEN (II)

function onTaffyRequest(verb, cfc, requestArguments, mimeExt, headers, methodMetadata, matchedURI) {

if (not structKeyExists(arguments.requestArguments, "apiKey") || !Len(arguments.requestArguments["apiKey"])) { return noData().withStatus(401, "Unauthorized"); }

if (!arrayFind(application.validAPIKeys, arguments.requestArguments["apiKey"])) { return noData().withStatus(403, "Forbidden"); }

return true; }

ADVANCED TASKS AND CONFIG

CUSTOM SERIALISERS (I)

▸ Default serialiser is JSON (CFML-server built-in)

▸ Extend from taffy.core.baseRepresentation

▸ Implement getAsX functions

▸ getAsXML, getAsJSON, getAsYML etc

ADVANCED TASKS AND CONFIG

CUSTOM SERIALISERS (II)

component extends="taffy.core.baseSerializer" { variables.anythingToXML = application.anythingToXML; variables.jsonUtil = application.jsonUtil;

function getAsXML() output="false" taffy_mime="application/xml" taffy_default="false" { return variables.anythingToXML.toXml(variables.data); }

function getAsJSON() output="false" taffy_mime="application/json" taffy_default="true" { return variables.jsonUtil.serialize(variables.data); } }

ADVANCED TASKS AND CONFIG

RATE LIMITS

▸ In a nutshell:

▸ manage access log (api key & time) in DB or key/value storage

▸ onTaffyRequest logs requests and check if limits are exceeded

▸ if not exceeded, process incoming request

▸ if exceeded, return status code “429 Too Many Requests”

ADVANCED TASKS AND CONFIG

API VERSIONING

▸ Multiple schools of thought:

▸ URI vs header

▸ Make sure you version from day 0!

▸ I tend to use /v1/product, /v2/product etc.

▸ Maps nicely on directory structures in /resources

▸ Update version when breaking compatibility

▸ Consider SemVer (Semantic Versioning) if your API is changing a lot

API BLUEPRINT AND OTHER TOOLS

https://www.flickr.com/photos/cardoso/2196726835/

API BLUEPRINT

WHAT IS IT?

▸ Description and specification language for web APIs (http://apiblueprint.org)

▸ From Apiary.io - but can be used independently from that

▸ Language spec: https://github.com/apiaryio/api-blueprint

▸ Offers platform-neutral documentation and additional tooling

▸ Aglio

▸ API-Mock

API BLUEPRINT

DOCUMENTATION WITH AGLIO

▸ Very simple markup language

▸ npm install -g aglio (https://www.npmjs.com/package/aglio)

▸ aglio -i documentation.md -o documentation.html

▸ MD structure:

▸ # Group

▸ ## Resource

▸ ### Action

API BLUEPRINT

MOCK SERVER WITH API-MOCK

▸ Creates a mock server for your API (https://github.com/localmed/api-mock)

▸ npm install -g api-mock

▸ api-mock ./documentation.md

▸ Defaults to port 3000

API BLUEPRINT

PAW

▸ REST API test and management tool on OS X

▸ Recording/Playback/Testing etc

▸ Has API Blueprint support through extensions

FINAL THOUGHTS

https://www.flickr.com/photos/brickset/16099265973/

FINAL THOUGHTS

WHAT DID WE COVER?

▸ Taffy - what is it?

▸ How to setup and the foundations

▸ Discussed some advanced topics in more detail

▸ High-level overview of API Blueprint

FINAL THOUGHTS

GET IN TOUCH

Kai Koenig

Email: kai@ventego-creative.co.nz

Twitter: @AgentK