Building Awesome APIs in Grails 
By Chris Latimer 
© 2014 SpringOne 2GX. All rights reserved. Do not distribute without permission.
2 
What makes an 
API awesome?
3 
Is it using JSON 
payloads 
instead of XML?
Using this Template 
4 
Is it strict adherence to REST principles?
5 
API Fielding Score
6
7 Predictable and Consistent
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:
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" 
/>
10 
Stable Versions 
URI Based Accept Header 
/v1/endpoint 
! 
/v2/endpoint 
Accept-­‐Version: 
1.0 
! 
Accept-­‐Version: 
1.1 
Content Type 
Accept: 
application/vnd.your.api.v2+json 
! 
Accept: 
application/vnd.your.api.v2.1+json
11 
Predictable Response Codes 
2xx Successful 4xx Client Error 
400 
Bad 
Request 
401 
Unauthorized 
403 
Forbidden 
404 
Not 
Found 
5xx Server Error 
500 
Server 
Error 
502 
Bad 
Gateway 
503 
Unavailable 
200 
Success 
201 
Created 
!
12 
Intuitive Structure
13 
Intuitive URI Structure 
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
14 
Intuitive Navigation 
Pagination 
"total": 
659212, 
"page": 
2, 
"per_page": 
10, 
"paging": 
{ 
"next": 
"/channels?page=3", 
"previous": 
"/channels?page=1", 
"first": 
"/channels?page=1", 
"last": 
"/channels?page=65922" 
}
15 
Intuitive Navigation 
Related Resources 
{ 
“uri": 
"/categories/experimental", 
"name": 
"Experimental", 
"subcategories": 
[ 
{ 
"uri": 
“/categories/experimental/animation", 
"name": 
"Animation", 
"link": 
“https://vimeo.com/categories/…” 
}… 
] 
}
Flexible Responses
17 
Partial Responses 
Get Full Response 
/feeds/api/users/default/uploads 
Get Partial Response 
/feeds/api/users/default/uploads? 
 
fields=entry(title,gd:comments,yt:statistics)
18 
Result Filtering 
Get List of Videos 
/feeds/api/videos?q=surfing&max-­‐results=10 
Get Videos with 1,000,000+ Views 
/feeds/api/videos?q=surfing&max-­‐results=10 
&fields=entry[yt:statistics/@viewCount 
> 
1000000]
19 
Customized Responses 
ItemLookup - Default 
ItemId=B00008OE6I 
ItemLookup - Default With Reviews 
ItemId=B00008OE6I 
&ResponseGroup=Reviews 
ItemLookup - Large With Reviews and Offers 
ItemId=B00008OE6I 
&ResponseGroup=Large,Reviews,Offers
20 
Easy to Learn and 
Experiment With
21
22
23 
Designing an 
awesome API
Apps API
G
Phone Shopping App 
Potential Resources 
/phones 
! 
/devices 
! 
/manufacturers
Phone Shopping App 
API Features 
Pagination 
! 
Filtering 
! 
Versioning
Phone Shopping App 
Potential Resources 
/phoneVariations 
! 
/phone/{id} 
! 
/phone/{id}/variations
Phone Shopping App 
API Features 
Complete 
vs. 
Compact 
representations 
! 
Including 
related 
entities 
! 
Linking 
to 
other 
resources
Phone API Endpoints 
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 Versioning 
Header Based 
Accept-­‐Version: 
1.0 
! 
Accept-­‐Version: 
2.0
General Response Structure Patterns 
{ 
“entity” 
: 
{ 
“attr1” 
: 
“value1”, 
“attr2” 
: 
“value2”, 
… 
}, 
“metadata” 
: 
{ 
} 
} 
{ 
“attr1” 
: 
“value1”, 
“attr2” 
: 
“value2”, 
… 
}
General Response Structure Patterns 
{ 
“entities” 
: 
[ 
{ 
“attr1” 
: 
“value1”, 
“attr2” 
: 
“value2”, 
… 
},{…},{…}… 
], 
“metadata” 
: 
{ 
} 
} 
[ 
{ 
“attr1” 
: 
“value1”, 
“attr2” 
: 
“value2”, 
… 
}, 
{…},{…}… 
]
API Formats 
JSON Default 
“phones” 
: 
[ 
{ 
“name”:“iPhone 
5s”, 
“basePrice”:599.99, 
… 
} 
] 
XML by Request 
<phones> 
<phone> 
<name>iPhone 
5s</name> 
… 
</phone> 
… 
</phones>
Intuitive Response Structures 
Single Response Collection Response 
{ 
{ 
“phones” 
: 
[ 
{…}, 
{…}, 
…], 
“paging” 
: 
{ 
… 
} 
} 
phone: 
{ 
“key1” 
: 
“value1”, 
… 
“keyN” 
: 
“valueN”, 
“links” 
: 
[ 
{…},{…}…] 
} 
}
Complete and Compact Representations 
Attribute Compact Complete 
name 
basePrice 
variations 
manufacturer
Customized Responses 
GET 
/phones/1 
{ 
“name” 
: 
“iPhone5s”, 
… 
} 
GET 
/phones/1?include=accessories 
{ 
“name” 
: 
“iPhone5s”, 
… 
“includes” 
: 
[ 
{ 
“accessories” 
: 
[…] 
} 
] 
}
Pagination 
GET 
/phones 
{ 
“phones” 
: 
[ 
{…}, 
{…}, 
…], 
“paging” 
: 
{ 
“totalCount” 
: 
233, 
“currentMax” 
: 
10, 
“currentOffset” 
: 
40 
} 
}
Linking to Related Entities 
GET 
/phones/1 
{ 
…, 
“links” 
: 
[ 
{ 
“rel” 
: 
“self”, 
“href” 
: 
“http://…”, 
}, 
{ 
“rel” 
: 
“manufacturer”, 
“href” 
: 
“http://…” 
} 
] 
}
Building an 
awesome API using
Phone 
Manufacturer 
Variation 
Domain Model
API Features 
• Accept Header Versioning 
• Predictable Response Codes 
• JSON and XML 
• RESTful URIs 
• Multiple Representations 
• Pagination 
• Custom Responses 
• Related Links
Out of the Box APIs 
@Resource(uri="/phones", 
formats=["json", 
"xml"]) 
class 
Phone 
{ 
… 
}
@Resource URL Mappings 
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
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
Link is on Twitter - @chrislatimer
Demo 
> 
git 
clone 
https://github.com/chrislatimer/gmobile.git 
! 
> 
cd 
gmobile 
! 
> 
git 
checkout 
api-­‐step-­‐1
API Features 
• Accept Header Versioning 
• Multiple Representations 
• Pagination 
• Custom Responses 
• Related Links 
• Predictable Response Codes 
• JSON and XML 
• RESTful URIs
Implementing API Versioning 
Version 1! 
Controller 
Version 2! 
Controller 
API Requests URL Mappings
Controller Namespaces 
Version 
1 
Controller 
class 
PhoneController 
extends 
RestfulController<Phone> 
{ 
static 
namespace 
= 
'v1' 
} 
Version 
2 
Controller 
class 
PhoneController 
extends 
RestfulController<Phone> 
{ 
static 
namespace 
= 
'v2' 
}
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
Demo 
> 
git 
checkout 
api-­‐step-­‐2
API Features 
• Multiple Representations 
• Pagination 
• Custom Responses 
• Related Links 
• Predictable Response Codes 
• JSON and XML 
• RESTful URIs 
• Accept Header Versioning
Peanut - Ugliest Dog 2014
! 
"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?
API Features 
• Multiple Representations 
• Pagination 
• Custom Responses 
• Related Links 
• Prettier JSON 
• Predictable Response Codes 
• JSON and XML 
• RESTful URIs 
• Accept Header Versioning
How can we improve our ! 
response payload?
Out of the Box Customization 
include 
attributes exclude 
attributes 
beans 
{ 
bookRenderer(XmlRenderer, 
Book) 
{ 
includes 
= 
[‘title’] 
} 
} 
beans 
{ 
bookRenderer(XmlRenderer, 
Book) 
{ 
excludes 
= 
[‘title’] 
} 
} 
This can be useful for very! 
simple customization
Simplified Picture of the Response Flow 
Controller Renderer Converter Marshaller 
as 
JSON/XML 
respond 
render 
value 
marshal
Creating a Custom Marshaller 
class 
PhoneMarshallerJson 
extends 
ClosureObjectMarshaller<JSON> 
{ 
! 
static 
final 
marshal 
= 
{ 
Phone 
phone 
-­‐> 
def 
map 
= 
[:] 
… 
map 
} 
! 
public 
PhoneMarshallerJson() 
{ 
super(Phone, 
marshal) 
} 
}
Creating a Custom Marshaller 
class 
PhoneMarshallerXml 
extends 
ClosureObjectMarshaller<XML>{ 
! 
def 
static 
final 
marshal 
= 
{ 
Phone 
phone, 
XML 
xml 
-­‐> 
xml.build 
{ 
name(phone.name) 
} 
} 
! 
public 
PhoneMarshallerXml() 
{ 
super(Phone, 
marshal) 
} 
}
Registering a Custom Marshaller 
beans 
= 
{ 
customPhoneJsonMarshaller(ObjectMarshallerRegisterer) 
{ 
marshaller 
= 
new 
PhoneMarshallerJson() 
converterClass 
= 
JSON 
priority 
= 
1 
} 
! 
customPhoneXmlMarshaller(ObjectMarshallerRegisterer) 
{ 
marshaller 
= 
new 
PhoneMarshallerXml() 
converterClass 
= 
XML 
priority 
= 
1 
} 
}
Demo 
> 
git 
checkout 
api-­‐step-­‐3
API Features 
• Multiple Representations 
• Pagination 
• Custom Responses 
• Related Links 
• Predictable Response Codes 
• JSON and XML 
• RESTful URIs 
• Accept Header Versioning 
• Prettier JSON
Creating Named Marshallers 
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) 
} 
! 
}
Registering Named Marshallers 
def 
init 
= 
{ 
JSON.createNamedConfig('compact') 
{ 
it.registerObjectMarshaller(Phone, 
PhoneMarshallerJsonCompact.marshal) 
} 
! 
JSON.createNamedConfig('complete') 
{ 
it.registerObjectMarshaller(Phone, 
PhoneMarshallerJson.marshal) 
} 
}
Demo 
> 
git 
checkout 
api-­‐step-­‐4
API Features 
• Pagination 
• Custom Responses 
• Related Links 
• Predictable Response Codes 
• JSON and XML 
• RESTful URIs 
• Accept Header Versioning 
• Prettier JSON 
• Multiple Representations
Creating a Custom Renderer 
class 
ApiJsonRenderer<T> 
extends 
AbstractRenderer<T> 
{ 
! 
public 
ApiJsonRenderer(Class<T> 
targetClass) 
{ 
super(targetClass, 
MimeType.JSON); 
} 
! 
@Override 
void 
render(T 
object, 
RenderContext 
context) 
{ 
// 
rendering 
logic 
} 
}
Using a Custom Renderer 
def 
show(Phone 
phone) 
{ 
def 
detail 
= 
params.detail 
?: 
"complete" 
withFormat 
{ 
json 
{ 
respond(phone, 
[detail:detail]) 
} 
xml 
{ 
XML.use(params?.detail?.toLowerCase() 
?: 
"complete") 
{ 
respond 
phone 
} 
} 
} 
}
Registering a Custom Renderer 
beans 
= 
{ 
phoneRenderer(ApiJsonRenderer, 
Phone) 
}
Demo 
> 
git 
checkout 
api-­‐step-­‐5
The monkey wrench in ! 
grails.converters.JSON
Creating a Custom Converter 
class 
ApiJSON 
extends 
JSON 
{ 
… 
public 
void 
renderPartial(JSONWriter 
out) 
{ 
initWriter(out) 
super.value(target) 
} 
protected 
initWriter(JSONWriter 
out) 
{ 
writer 
= 
out 
referenceStack 
= 
new 
Stack<Object>(); 
} 
}
Demo 
> 
git 
checkout 
api-­‐step-­‐6
Rendering Paging Info 
def 
index() 
{ 
… 
withFormat 
{ 
json 
{ 
respond 
Phone.list(params), 
[detail:detail, 
paging:[totalCount: 
Phone.count(), 
currentMax: 
params.max, 
curentOffset:offset]] 
} 
…
Rendering Paging Info 
void 
render(T 
object, 
RenderContext 
context) 
{ 
… 
if(context.arguments?.paging) 
{ 
writer.key("paging") 
converter 
= 
context.arguments.paging 
as 
ApiJSON 
converter.renderPartial(writer) 
} 
… 
}
Demo 
> 
git 
checkout 
api-­‐step-­‐7
API Features 
• Custom Responses 
• Related Links 
• Predictable Response Codes 
• JSON and XML 
• RESTful URIs 
• Accept Header Versioning 
• Prettier JSON 
• Multiple Representations 
• Pagination
Customizing Responses 
def 
show(Phone 
phone) 
{ 
… 
withFormat 
{ 
json 
{ 
respond(phone, 
[detail:detail, 
include:params?.list('include')]) 
} 
… 
} 
}
Rendering Custom Responses 
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() 
} 
… 
}
Demo 
> 
git 
checkout 
api-­‐step-­‐8
API Features 
• Predictable Response Codes • Related Links 
• JSON and XML 
• RESTful URIs 
• Accept Header Versioning 
• Prettier JSON 
• Multiple Representations 
• Pagination 
• Custom Responses
Including Related Links 
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 
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 
closure.curry(linkGenerator)
Demo 
> 
git 
checkout 
api-­‐step-­‐9
API Features 
• Predictable Response Codes 
• JSON and XML 
• RESTful URIs 
• Accept Header Versioning 
• Prettier JSON 
• Multiple Representations 
• Pagination 
• Custom Responses 
• Related Links
Is this API Awesome? 
It’s getting there…
Follow up questions? 
@chrislatimer 
clatimer@apigee.com

Building Awesome APIs in Grails

  • 1.
    Building Awesome APIsin Grails By Chris Latimer © 2014 SpringOne 2GX. All rights reserved. Do not distribute without permission.
  • 2.
    2 What makesan API awesome?
  • 3.
    3 Is itusing JSON payloads instead of XML?
  • 4.
    Using this Template 4 Is it strict adherence to REST principles?
  • 5.
  • 6.
  • 7.
  • 8.
    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:
  • 9.
    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" />
  • 10.
    10 Stable Versions URI Based Accept Header /v1/endpoint ! /v2/endpoint Accept-­‐Version: 1.0 ! Accept-­‐Version: 1.1 Content Type Accept: application/vnd.your.api.v2+json ! Accept: application/vnd.your.api.v2.1+json
  • 11.
    11 Predictable ResponseCodes 2xx Successful 4xx Client Error 400 Bad Request 401 Unauthorized 403 Forbidden 404 Not Found 5xx Server Error 500 Server Error 502 Bad Gateway 503 Unavailable 200 Success 201 Created !
  • 12.
  • 13.
    13 Intuitive URIStructure 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
  • 14.
    14 Intuitive Navigation Pagination "total": 659212, "page": 2, "per_page": 10, "paging": { "next": "/channels?page=3", "previous": "/channels?page=1", "first": "/channels?page=1", "last": "/channels?page=65922" }
  • 15.
    15 Intuitive Navigation Related Resources { “uri": "/categories/experimental", "name": "Experimental", "subcategories": [ { "uri": “/categories/experimental/animation", "name": "Animation", "link": “https://vimeo.com/categories/…” }… ] }
  • 16.
  • 17.
    17 Partial Responses Get Full Response /feeds/api/users/default/uploads Get Partial Response /feeds/api/users/default/uploads? fields=entry(title,gd:comments,yt:statistics)
  • 18.
    18 Result Filtering Get List of Videos /feeds/api/videos?q=surfing&max-­‐results=10 Get Videos with 1,000,000+ Views /feeds/api/videos?q=surfing&max-­‐results=10 &fields=entry[yt:statistics/@viewCount > 1000000]
  • 19.
    19 Customized Responses ItemLookup - Default ItemId=B00008OE6I ItemLookup - Default With Reviews ItemId=B00008OE6I &ResponseGroup=Reviews ItemLookup - Large With Reviews and Offers ItemId=B00008OE6I &ResponseGroup=Large,Reviews,Offers
  • 20.
    20 Easy toLearn and Experiment With
  • 21.
  • 22.
  • 23.
    23 Designing an awesome API
  • 24.
  • 25.
  • 26.
    Phone Shopping App Potential Resources /phones ! /devices ! /manufacturers
  • 27.
    Phone Shopping App API Features Pagination ! Filtering ! Versioning
  • 28.
    Phone Shopping App Potential Resources /phoneVariations ! /phone/{id} ! /phone/{id}/variations
  • 29.
    Phone Shopping App API Features Complete vs. Compact representations ! Including related entities ! Linking to other resources
  • 30.
    Phone API Endpoints 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
  • 31.
    Phone API Versioning Header Based Accept-­‐Version: 1.0 ! Accept-­‐Version: 2.0
  • 32.
    General Response StructurePatterns { “entity” : { “attr1” : “value1”, “attr2” : “value2”, … }, “metadata” : { } } { “attr1” : “value1”, “attr2” : “value2”, … }
  • 33.
    General Response StructurePatterns { “entities” : [ { “attr1” : “value1”, “attr2” : “value2”, … },{…},{…}… ], “metadata” : { } } [ { “attr1” : “value1”, “attr2” : “value2”, … }, {…},{…}… ]
  • 34.
    API Formats JSONDefault “phones” : [ { “name”:“iPhone 5s”, “basePrice”:599.99, … } ] XML by Request <phones> <phone> <name>iPhone 5s</name> … </phone> … </phones>
  • 35.
    Intuitive Response Structures Single Response Collection Response { { “phones” : [ {…}, {…}, …], “paging” : { … } } phone: { “key1” : “value1”, … “keyN” : “valueN”, “links” : [ {…},{…}…] } }
  • 36.
    Complete and CompactRepresentations Attribute Compact Complete name basePrice variations manufacturer
  • 37.
    Customized Responses GET /phones/1 { “name” : “iPhone5s”, … } GET /phones/1?include=accessories { “name” : “iPhone5s”, … “includes” : [ { “accessories” : […] } ] }
  • 38.
    Pagination GET /phones { “phones” : [ {…}, {…}, …], “paging” : { “totalCount” : 233, “currentMax” : 10, “currentOffset” : 40 } }
  • 39.
    Linking to RelatedEntities GET /phones/1 { …, “links” : [ { “rel” : “self”, “href” : “http://…”, }, { “rel” : “manufacturer”, “href” : “http://…” } ] }
  • 40.
  • 41.
  • 42.
    API Features •Accept Header Versioning • Predictable Response Codes • JSON and XML • RESTful URIs • Multiple Representations • Pagination • Custom Responses • Related Links
  • 43.
    Out of theBox APIs @Resource(uri="/phones", formats=["json", "xml"]) class Phone { … }
  • 44.
    @Resource URL Mappings 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
  • 45.
    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
  • 47.
    Link is onTwitter - @chrislatimer
  • 48.
    Demo > git clone https://github.com/chrislatimer/gmobile.git ! > cd gmobile ! > git checkout api-­‐step-­‐1
  • 49.
    API Features •Accept Header Versioning • Multiple Representations • Pagination • Custom Responses • Related Links • Predictable Response Codes • JSON and XML • RESTful URIs
  • 50.
    Implementing API Versioning Version 1! Controller Version 2! Controller API Requests URL Mappings
  • 51.
    Controller Namespaces Version 1 Controller class PhoneController extends RestfulController<Phone> { static namespace = 'v1' } Version 2 Controller class PhoneController extends RestfulController<Phone> { static namespace = 'v2' }
  • 52.
    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
  • 53.
    Demo > git checkout api-­‐step-­‐2
  • 54.
    API Features •Multiple Representations • Pagination • Custom Responses • Related Links • Predictable Response Codes • JSON and XML • RESTful URIs • Accept Header Versioning
  • 55.
  • 56.
    ! "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?
  • 57.
    API Features •Multiple Representations • Pagination • Custom Responses • Related Links • Prettier JSON • Predictable Response Codes • JSON and XML • RESTful URIs • Accept Header Versioning
  • 58.
    How can weimprove our ! response payload?
  • 59.
    Out of theBox Customization include attributes exclude attributes beans { bookRenderer(XmlRenderer, Book) { includes = [‘title’] } } beans { bookRenderer(XmlRenderer, Book) { excludes = [‘title’] } } This can be useful for very! simple customization
  • 60.
    Simplified Picture ofthe Response Flow Controller Renderer Converter Marshaller as JSON/XML respond render value marshal
  • 61.
    Creating a CustomMarshaller class PhoneMarshallerJson extends ClosureObjectMarshaller<JSON> { ! static final marshal = { Phone phone -­‐> def map = [:] … map } ! public PhoneMarshallerJson() { super(Phone, marshal) } }
  • 62.
    Creating a CustomMarshaller class PhoneMarshallerXml extends ClosureObjectMarshaller<XML>{ ! def static final marshal = { Phone phone, XML xml -­‐> xml.build { name(phone.name) } } ! public PhoneMarshallerXml() { super(Phone, marshal) } }
  • 63.
    Registering a CustomMarshaller beans = { customPhoneJsonMarshaller(ObjectMarshallerRegisterer) { marshaller = new PhoneMarshallerJson() converterClass = JSON priority = 1 } ! customPhoneXmlMarshaller(ObjectMarshallerRegisterer) { marshaller = new PhoneMarshallerXml() converterClass = XML priority = 1 } }
  • 64.
    Demo > git checkout api-­‐step-­‐3
  • 65.
    API Features •Multiple Representations • Pagination • Custom Responses • Related Links • Predictable Response Codes • JSON and XML • RESTful URIs • Accept Header Versioning • Prettier JSON
  • 66.
    Creating Named Marshallers 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) } ! }
  • 67.
    Registering Named Marshallers def init = { JSON.createNamedConfig('compact') { it.registerObjectMarshaller(Phone, PhoneMarshallerJsonCompact.marshal) } ! JSON.createNamedConfig('complete') { it.registerObjectMarshaller(Phone, PhoneMarshallerJson.marshal) } }
  • 68.
    Demo > git checkout api-­‐step-­‐4
  • 69.
    API Features •Pagination • Custom Responses • Related Links • Predictable Response Codes • JSON and XML • RESTful URIs • Accept Header Versioning • Prettier JSON • Multiple Representations
  • 70.
    Creating a CustomRenderer class ApiJsonRenderer<T> extends AbstractRenderer<T> { ! public ApiJsonRenderer(Class<T> targetClass) { super(targetClass, MimeType.JSON); } ! @Override void render(T object, RenderContext context) { // rendering logic } }
  • 71.
    Using a CustomRenderer def show(Phone phone) { def detail = params.detail ?: "complete" withFormat { json { respond(phone, [detail:detail]) } xml { XML.use(params?.detail?.toLowerCase() ?: "complete") { respond phone } } } }
  • 72.
    Registering a CustomRenderer beans = { phoneRenderer(ApiJsonRenderer, Phone) }
  • 73.
    Demo > git checkout api-­‐step-­‐5
  • 74.
    The monkey wrenchin ! grails.converters.JSON
  • 75.
    Creating a CustomConverter class ApiJSON extends JSON { … public void renderPartial(JSONWriter out) { initWriter(out) super.value(target) } protected initWriter(JSONWriter out) { writer = out referenceStack = new Stack<Object>(); } }
  • 76.
    Demo > git checkout api-­‐step-­‐6
  • 77.
    Rendering Paging Info def index() { … withFormat { json { respond Phone.list(params), [detail:detail, paging:[totalCount: Phone.count(), currentMax: params.max, curentOffset:offset]] } …
  • 78.
    Rendering Paging Info void render(T object, RenderContext context) { … if(context.arguments?.paging) { writer.key("paging") converter = context.arguments.paging as ApiJSON converter.renderPartial(writer) } … }
  • 79.
    Demo > git checkout api-­‐step-­‐7
  • 80.
    API Features •Custom Responses • Related Links • Predictable Response Codes • JSON and XML • RESTful URIs • Accept Header Versioning • Prettier JSON • Multiple Representations • Pagination
  • 81.
    Customizing Responses def show(Phone phone) { … withFormat { json { respond(phone, [detail:detail, include:params?.list('include')]) } … } }
  • 82.
    Rendering Custom Responses 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() } … }
  • 83.
    Demo > git checkout api-­‐step-­‐8
  • 84.
    API Features •Predictable Response Codes • Related Links • JSON and XML • RESTful URIs • Accept Header Versioning • Prettier JSON • Multiple Representations • Pagination • Custom Responses
  • 85.
    Including Related Links 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 }
  • 86.
    Including Related Links 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 }
  • 87.
    Including Related Links closure.curry(linkGenerator)
  • 88.
    Demo > git checkout api-­‐step-­‐9
  • 89.
    API Features •Predictable Response Codes • JSON and XML • RESTful URIs • Accept Header Versioning • Prettier JSON • Multiple Representations • Pagination • Custom Responses • Related Links
  • 90.
    Is this APIAwesome? It’s getting there…
  • 91.
    Follow up questions? @chrislatimer clatimer@apigee.com