Building Awesome APIs in Grails

91
© 2014 SpringOne 2GX. All rights reserved. Do not distribute without permission. Building Awesome APIs in Grails By Chris Latimer

description

 

Transcript of Building Awesome APIs in Grails

Page 1: Building Awesome APIs in Grails

© 2014 SpringOne 2GX. All rights reserved. Do not distribute without permission.

Building Awesome APIs in GrailsBy Chris Latimer

Page 2: Building Awesome APIs in Grails

2

What makes an API awesome?

Page 3: Building Awesome APIs in Grails

3

Is it using JSON payloads

instead of XML?

Page 4: Building Awesome APIs in Grails

Using this Template

4

Is it strict adherence to REST principles?

Page 5: Building Awesome APIs in Grails

5

API Fielding Score

Page 6: Building Awesome APIs in Grails

6

Page 7: Building Awesome APIs in Grails

7Predictable and Consistent

Page 8: Building Awesome APIs in Grails

8

"uri":  "/categories/activism",  "name":  "Activism  &  Non  Profits",  "link":  “https://vimeo.com/…”,    …  "metadata":  {        "connections":  {…}  }

Category !Response:

"uri":  "/channels/804185",  "name":  "School  Intercom",  "link":  “https://vimeo.com/…”,        …  "metadata":  {        "connections":  {…}  }

Channel !Response:

Page 9: Building Awesome APIs in Grails

9

   <photo  id="2636"  owner="47058503995@N01"                secret="a123456"  server=“2"                    title=“test_04”  ispublic=“1"                    isfriend="0"  isfamily="0"  />

<contact  nsid="12037949629@N01"                      username="Eric"  iconserver="1"                      realname="Eric  Costello"  friend="1"                      family="0"  ignored="1"  />

Page 10: Building Awesome APIs in Grails

10

Stable Versions

/v1/endpoint  !

/v2/endpoint

Accept-­‐Version:  1.0  !

Accept-­‐Version:  1.1

URI Based Accept Header

Accept:  application/vnd.your.api.v2+json  !

Accept:  application/vnd.your.api.v2.1+json

Content Type

Page 11: Building Awesome APIs in Grails

11

Predictable Response Codes

400  Bad  Request  401  Unauthorized  403  Forbidden  404  Not  Found

2xx Successful 4xx Client Error

500  Server  Error  502  Bad  Gateway  503  Unavailable  

5xx Server Error

200  Success  201  Created  !

Page 12: Building Awesome APIs in Grails

12Intuitive Structure

Page 13: Building Awesome APIs in Grails

13

URI Description

/group/{id} A Facebook group

/group/{id}/feed This group’s feed

/group/{id}/files Files uploaded to this group

/group/{id}/events This group’s events

Intuitive URI Structure

Page 14: Building Awesome APIs in Grails

14

Intuitive Navigation

"total":  659212,  "page":  2,  "per_page":  10,  "paging":  {      "next":  "/channels?page=3",      "previous":  "/channels?page=1",      "first":  "/channels?page=1",      "last":  "/channels?page=65922"  }

Pagination

Page 15: Building Awesome APIs in Grails

15

Intuitive Navigation

{      “uri":  "/categories/experimental",      "name":  "Experimental",      "subcategories":  [          {              "uri":  “/categories/experimental/animation",              "name":  "Animation",              "link":  “https://vimeo.com/categories/…”          }…      ]  }

Related Resources

Page 16: Building Awesome APIs in Grails

Flexible Responses

Page 17: Building Awesome APIs in Grails

17

Partial Responses

/feeds/api/users/default/uploads

Get Full Response

/feeds/api/users/default/uploads?  \  fields=entry(title,gd:comments,yt:statistics)

Get Partial Response

Page 18: Building Awesome APIs in Grails

18

Result Filtering

/feeds/api/videos?q=surfing&max-­‐results=10  

Get List of Videos

/feeds/api/videos?q=surfing&max-­‐results=10    &fields=entry[yt:statistics/@viewCount  >  1000000]

Get Videos with 1,000,000+ Views

Page 19: Building Awesome APIs in Grails

19

Customized Responses

ItemId=B00008OE6I

ItemLookup - Default

ItemId=B00008OE6I  &ResponseGroup=Reviews

ItemLookup - Default With Reviews

ItemId=B00008OE6I  &ResponseGroup=Large,Reviews,Offers

ItemLookup - Large With Reviews and Offers

Page 20: Building Awesome APIs in Grails

20

Easy to Learn and Experiment With

Page 21: Building Awesome APIs in Grails

21

Page 22: Building Awesome APIs in Grails

22

Page 23: Building Awesome APIs in Grails

23

Designing an awesome API

Page 24: Building Awesome APIs in Grails

Apps API

Page 25: Building Awesome APIs in Grails

G

Page 26: Building Awesome APIs in Grails

Phone Shopping App

/phones  !

/devices  !

/manufacturers

Potential Resources

Page 27: Building Awesome APIs in Grails

Phone Shopping App

Pagination  !

Filtering  !

Versioning

API Features

Page 28: Building Awesome APIs in Grails

Phone Shopping App

/phoneVariations  !

/phone/{id}  !

/phone/{id}/variations

Potential Resources

Page 29: Building Awesome APIs in Grails

Phone Shopping App

Complete  vs.  Compact  representations  !

Including  related  entities  !

Linking  to  other  resources

API Features

Page 30: Building Awesome APIs in Grails

URI Verb Description

/phones GET Get a list of phones

/phones/{id} GET Get phone details

/phones/{id}/manufacturer GET Get phone’s manufacturer

/phones/{id}/variations GET Get variations of a phone

Phone API Endpoints

Page 31: Building Awesome APIs in Grails

Phone API Versioning

Accept-­‐Version:  1.0  !

Accept-­‐Version:  2.0

Header Based

Page 32: Building Awesome APIs in Grails

{      “entity”  :  {          “attr1”  :  “value1”,          “attr2”  :  “value2”,          …      },      “metadata”  :  {      }  }

{      “attr1”  :  “value1”,      “attr2”  :  “value2”,      …  }

General Response Structure Patterns

Page 33: Building Awesome APIs in Grails

{      “entities”  :  [          {              “attr1”  :  “value1”,              “attr2”  :  “value2”,              …          },{…},{…}…      ],      “metadata”  :  {      }  }

[      {          “attr1”  :  “value1”,          “attr2”  :  “value2”,          …      },      {…},{…}…  ]

General Response Structure Patterns

Page 34: Building Awesome APIs in Grails

API Formats

“phones”  :  [      {          “name”:“iPhone  5s”,          “basePrice”:599.99,          …      }  ]

JSON Default<phones>      <phone>        <name>iPhone  5s</name>        …      </phone>      …  </phones>

XML by Request

Page 35: Building Awesome APIs in Grails

Intuitive Response Structures

{      “phones”  :  [  {…},  {…},  …],      “paging”  :  {  …  }  }

Collection ResponseSingle Response{      phone:  {          “key1”  :  “value1”,          …          “keyN”  :  “valueN”,          “links”  :  [  {…},{…}…]      }  }

Page 36: Building Awesome APIs in Grails

Complete and Compact Representations

Attribute Compact Complete

name

basePrice

variations

manufacturer

Page 37: Building Awesome APIs in Grails

Customized Responses

{      “name”  :  “iPhone5s”,      …  }

GET  /phones/1

{      “name”  :  “iPhone5s”,      …      “includes”  :  [          {              “accessories”  :  […]          }      ]  }

GET  /phones/1?include=accessories

Page 38: Building Awesome APIs in Grails

PaginationGET  /phones{      “phones”  :  [  {…},  {…},  …],      “paging”  :  {          “totalCount”  :  233,          “currentMax”  :  10,          “currentOffset”  :  40      }  }

Page 39: Building Awesome APIs in Grails

GET  /phones/1{      …,      “links”  :  [          {              “rel”  :  “self”,              “href”  :  “http://…”,          },          {              “rel”  :  “manufacturer”,              “href”  :  “http://…”          }      ]  }

Linking to Related Entities

Page 40: Building Awesome APIs in Grails

Building an awesome API using

Page 41: Building Awesome APIs in Grails

Phone

Manufacturer

Variation

Domain Model

Page 42: Building Awesome APIs in Grails

API Features

• Accept Header Versioning

• Predictable Response Codes

• JSON and XML

• RESTful URIs

• Multiple Representations

• Pagination

• Custom Responses

• Related Links

Page 43: Building Awesome APIs in Grails

Out of the Box APIs

@Resource(uri="/phones",                        formats=["json",  "xml"])  class  Phone  {  …  }

Page 44: Building Awesome APIs in Grails

URL Mapping Verb Action

/phones GET List of phones

/phones POST Create new phone

/phones/{id} GET Get single phone

/phones/{id} PUT Update phone

/phones/{id} DELETE Delete phone

@Resource URL Mappings

Page 45: Building Awesome APIs in Grails

Read-Only URL Mappings

@Resource(uri="/phones",  formats=["json",  "xml"],                        readOnly=true)

URL Mapping Verb Action

/phones GET List of phones

/phones/{id} GET Get single phone

Page 46: Building Awesome APIs in Grails
Page 47: Building Awesome APIs in Grails

Link is on Twitter - @chrislatimer

Page 48: Building Awesome APIs in Grails

Demo

>  git  clone  https://github.com/chrislatimer/gmobile.git  !

>  cd  gmobile  !

>  git  checkout  api-­‐step-­‐1

Page 49: Building Awesome APIs in Grails

API Features

• Accept Header Versioning

• Multiple Representations

• Pagination

• Custom Responses

• Related Links

• Predictable Response Codes

• JSON and XML

• RESTful URIs

Page 50: Building Awesome APIs in Grails

Implementing API Versioning

Version 1!Controller

Version 2!Controller

URL MappingsAPI Requests

Page 51: Building Awesome APIs in Grails

Controller Namespaces

class  PhoneController  extends  RestfulController<Phone>  {          static  namespace  =  'v1'  }

Version  1  Controller

Version  2  Controllerclass  PhoneController  extends  RestfulController<Phone>  {          static  namespace  =  'v2'  }

Page 52: Building Awesome APIs in Grails

URL Mappings"/phones"(version:'1.0',  resources:"phone",  namespace:'v1')  !

"/phones"(version:'2.0',  resources:"phone",  namespace:'v2')

Version 1!Controller

Version 2!Controller

Accept-­‐Version:  1.0

Accept-­‐Version:  2.0

Page 53: Building Awesome APIs in Grails

Demo

>  git  checkout  api-­‐step-­‐2

Page 54: Building Awesome APIs in Grails

API Features

• Multiple Representations

• Pagination

• Custom Responses

• Related Links

• Predictable Response Codes

• JSON and XML

• RESTful URIs

• Accept Header Versioning

Page 55: Building Awesome APIs in Grails

Peanut - Ugliest Dog 2014

Page 56: Building Awesome APIs in Grails

!

   "class":  "org.gmobile.Phone",      "id":  1,      "description":  null,      "manufacturer":  {          "class":  "org.gmobile.Manufacturer",          "id":  1      },      "name":  "Xtreme  Photon  Z5",      "variations":  [          {              "class":  "org.gmobile.Variation",              "id":  1          },…      ]

Ugliest JSON 2014?

Page 57: Building Awesome APIs in Grails

API Features

• Multiple Representations

• Pagination

• Custom Responses

• Related Links

• Prettier JSON

• Predictable Response Codes

• JSON and XML

• RESTful URIs

• Accept Header Versioning

Page 58: Building Awesome APIs in Grails

How can we improve our !response payload?

Page 59: Building Awesome APIs in Grails

Out of the Box Customization

beans  {      bookRenderer(XmlRenderer,  Book)  {              includes  =  [‘title’]      }  }

include  attributes exclude  attributes

This can be useful for very!simple customization

beans  {      bookRenderer(XmlRenderer,  Book)  {              excludes  =  [‘title’]      }  }

Page 60: Building Awesome APIs in Grails

Simplified Picture of the Response Flow

Controller Renderer Converter Marshaller

as  JSON/XML

respond

render

valuemarshal

Page 61: Building Awesome APIs in Grails

class  PhoneMarshallerJson  extends  ClosureObjectMarshaller<JSON>  {  !

       static  final  marshal  =  {  Phone  phone  -­‐>                  def  map  =  [:]                  …                  map          }  !

       public  PhoneMarshallerJson()  {                  super(Phone,  marshal)          }  }

Creating a Custom Marshaller

Page 62: Building Awesome APIs in Grails

class  PhoneMarshallerXml  extends  ClosureObjectMarshaller<XML>{  !

       def  static  final  marshal  =  {  Phone  phone,  XML  xml  -­‐>                  xml.build  {                          name(phone.name)                  }          }  !

       public  PhoneMarshallerXml()  {                  super(Phone,  marshal)          }  }

Creating a Custom Marshaller

Page 63: Building Awesome APIs in Grails

beans  =  {          customPhoneJsonMarshaller(ObjectMarshallerRegisterer)  {                  marshaller  =  new  PhoneMarshallerJson()                  converterClass  =  JSON                  priority  =  1          }  !

       customPhoneXmlMarshaller(ObjectMarshallerRegisterer)  {                  marshaller  =  new  PhoneMarshallerXml()                  converterClass  =  XML                  priority  =  1          }  }

Registering a Custom Marshaller

Page 64: Building Awesome APIs in Grails

Demo

>  git  checkout  api-­‐step-­‐3

Page 65: Building Awesome APIs in Grails

API Features

• Multiple Representations

• Pagination

• Custom Responses

• Related Links

• Predictable Response Codes

• JSON and XML

• RESTful URIs

• Accept Header Versioning

• Prettier JSON

Page 66: Building Awesome APIs in Grails

class  PhoneMarshallerJsonCompact  extends  ClosureObjectMarshaller<JSON>{  !

       public  static  final  marshal  =  {  Phone  phone  -­‐>                  def  map  =  [:]                  map.id  =  phone.id                  map.name  =  phone.name                  map          }  !

       public  PhoneMarshallerJsonCompact()  {                  super(Phone,  marshal)          }  !

}

Creating Named Marshallers

Page 67: Building Awesome APIs in Grails

def  init  =  {      JSON.createNamedConfig('compact')  {          it.registerObjectMarshaller(Phone,  PhoneMarshallerJsonCompact.marshal)      }  !

   JSON.createNamedConfig('complete')  {          it.registerObjectMarshaller(Phone,  PhoneMarshallerJson.marshal)      }  }

Registering Named Marshallers

Page 68: Building Awesome APIs in Grails

Demo

>  git  checkout  api-­‐step-­‐4

Page 69: Building Awesome APIs in Grails

API Features

• Pagination

• Custom Responses

• Related Links

• Predictable Response Codes

• JSON and XML

• RESTful URIs

• Accept Header Versioning

• Prettier JSON

• Multiple Representations

Page 70: Building Awesome APIs in Grails

class  ApiJsonRenderer<T>  extends  AbstractRenderer<T>  {  !

       public  ApiJsonRenderer(Class<T>  targetClass)  {                  super(targetClass,  MimeType.JSON);          }  !

       @Override          void  render(T  object,  RenderContext  context)  {              //  rendering  logic          }  }

Creating a Custom Renderer

Page 71: Building Awesome APIs in Grails

def  show(Phone  phone)  {          def  detail  =  params.detail  ?:  "complete"          withFormat  {                  json  {                          respond(phone,  [detail:detail])                  }                  xml  {                          XML.use(params?.detail?.toLowerCase()  ?:  "complete")  {                                  respond  phone                          }                  }          }  }

Using a Custom Renderer

Page 72: Building Awesome APIs in Grails

beans  =  {          phoneRenderer(ApiJsonRenderer,  Phone)  }

Registering a Custom Renderer

Page 73: Building Awesome APIs in Grails

Demo

>  git  checkout  api-­‐step-­‐5

Page 74: Building Awesome APIs in Grails

The monkey wrench in !grails.converters.JSON

Page 75: Building Awesome APIs in Grails

class  ApiJSON  extends  JSON  {          …              public  void  renderPartial(JSONWriter  out)  {                  initWriter(out)                  super.value(target)          }              protected  initWriter(JSONWriter  out)  {                  writer  =  out                  referenceStack  =  new  Stack<Object>();          }  }  

Creating a Custom Converter

Page 76: Building Awesome APIs in Grails

Demo

>  git  checkout  api-­‐step-­‐6

Page 77: Building Awesome APIs in Grails

def  index()  {  …      withFormat  {          json  {                  respond  Phone.list(params),                                    [detail:detail,                                    paging:[totalCount:  Phone.count(),                                                    currentMax:  params.max,                                                    curentOffset:offset]]          }  …

Rendering Paging Info

Page 78: Building Awesome APIs in Grails

void  render(T  object,  RenderContext  context)  {          …          if(context.arguments?.paging)  {                  writer.key("paging")                  converter  =  context.arguments.paging  as  ApiJSON                  converter.renderPartial(writer)          }          …  }

Rendering Paging Info

Page 79: Building Awesome APIs in Grails

Demo

>  git  checkout  api-­‐step-­‐7

Page 80: Building Awesome APIs in Grails

API Features

• Custom Responses

• Related Links

• Predictable Response Codes

• JSON and XML

• RESTful URIs

• Accept Header Versioning

• Prettier JSON

• Multiple Representations

• Pagination

Page 81: Building Awesome APIs in Grails

def  show(Phone  phone)  {          …          withFormat  {                  json  {                          respond(phone,  [detail:detail,                                                            include:params?.list('include')])                  }                  …          }  }

Customizing Responses

Page 82: Building Awesome APIs in Grails

void  render(T  object,  RenderContext  context)  {      …      if(context.arguments?.include)  {              writer.key("include")              writer.array()              context.arguments?.include.each  {  includeProp  -­‐>                      JSON.use("compact")  {                          converter  =  object.properties.get(includeProp)  as  ApiJSON                      }                      writer.object()                      writer.key(includeProp)                      converter.renderPartial(writer)                      writer.endObject()              }            writer.endArray()      }      …  }

Rendering Custom Responses

Page 83: Building Awesome APIs in Grails

Demo

>  git  checkout  api-­‐step-­‐8

Page 84: Building Awesome APIs in Grails

API Features

• Related Links• Predictable Response Codes

• JSON and XML

• RESTful URIs

• Accept Header Versioning

• Prettier JSON

• Multiple Representations

• Pagination

• Custom Responses

Page 85: Building Awesome APIs in Grails

static  final  Closure  marshal  =  {  LinkGenerator  linkGenerator,  Phone  phone  -­‐>                  def  json  =  [:]                  json.id  =  phone.id                  json.name  =  phone.name                                    json.links  =  []                  json.links  <<  [rel:"self",                                                    href:linkGenerator.link(resource:  phone,                                                  method:  HttpMethod.GET,  absolute:  true)]                  json  }

Including Related Links

Page 86: Building Awesome APIs in Grails

static  final  Closure  marshal  =  {  LinkGenerator  linkGenerator,  Phone  phone  -­‐>                  def  json  =  [:]                  json.id  =  phone.id                  json.name  =  phone.name                                    json.links  =  []                  json.links  <<  [rel:"self",                                                    href:linkGenerator.link(resource:  phone,                                                  method:  HttpMethod.GET,  absolute:  true)]                  json  }

Including Related Links

Page 87: Building Awesome APIs in Grails

closure.curry(linkGenerator)

Including Related Links

Page 88: Building Awesome APIs in Grails

Demo

>  git  checkout  api-­‐step-­‐9

Page 89: Building Awesome APIs in Grails

API Features

• Predictable Response Codes

• JSON and XML

• RESTful URIs

• Accept Header Versioning

• Prettier JSON

• Multiple Representations

• Pagination

• Custom Responses

• Related Links

Page 90: Building Awesome APIs in Grails

Is this API Awesome?

It’s getting there…

Page 91: Building Awesome APIs in Grails

Follow up questions?

@chrislatimer

[email protected]