Building Next-Gen Web APIs
with JSON-LD and Hydra
Markus Lanthaler
Why do we
need a website?
Of course we
have a website
Why do we
need an API?
1995 2000 2005 2010
Of course we
have an API
Adapted from T. Vitvar’s and J. Musser’s ECOWS 2010 Keynote,
“ProgrammableWeb.com:Statistics, Trends, and Best Practices”
Using Web APIs is still challenging
Level 0:The Swamp of POX
Level 1: Resources
Level 2: HTTPVerbs
{
"id": "cso29ax",
"title": "Symfony Live Portland 2013",
"description": "Prepare slides",
"is_open": true,
"created_at": "2012-11-26T04:49:44Z"
}
http://example.com/issues/cso29ax
BILLION DOLLAR
QUESTION
the
{
"id": "cso29ax",
"title": "Symfony Live Portland 2013",
"description": "Prepare slides",
"is_open": true,
"created_at": "2012-11-26T04:49:44Z"
}
http://example.com/issues/cso29ax
http://example.com/issue/{id}/comments/
{
"id": "cso29ax",
"title": "Symfony Live Portland 2013",
"description": "Prepare slides",
"is_open": true,
"created_at": "2012-11-26T04:49:44Z",
"comments": "/issues/cso29ax/comments/"
}
http://example.com/issues/cso29ax
Level 0:The Swamp of POX
Level 1: Resources
Level 2: HTTPVerbs
Level 3: Hypermedia Controls
{
"id": "cso29ax",
"title": "Symfony Live Portland 2013",
"description": "Prepare slides",
"is_open": true,
"created_at": "2012-11-26T04:49:44Z",
"comments": "/issues/cso29ax/comments/"
}
http://example.com/issues/cso29ax
Every API is a snowflake
Result: tightly coupled & brittle systems
{
"id": "cso29ax",
"title": "Symfony Live Portland 2013",
"description": "Prepare slides",
"is_open": true,
"created_at": "2012-11-26T04:49:44Z",
"comments": "/issues/cso29ax/comments/"
}
http://example.com/issues/cso29ax
{
69 64: 63 73 6f 32 39 61 78
74 69 74 6c 65: 53 79 6d 66 6f 6e 79 20 4c 69 76 …
64 65 73 63 72 69 70 74 69 6f 6e: 50 72 65 70 61 …
69 73 5f 6f 70 65 6e: 01
63 72 65 61 74 65 64 5f 61 74: 32 30 31 32 2d 31 …
63 6f 6d 6d 65 6e 74 73: 2f 69 73 73 75 65 73 2f …
}
http://example.com/issues/cso29ax
Identifiers on the Web: URIs
Linked Data Principles
Tim Berners-Lee, 2006
JSON-LD
Make data self-descriptive by
mapping concepts to URLs
{
"id": "markus",
"firstname": "Markus",
"lastname": "Lanthaler",
"homepage": "http://www.markus-lanthaler.com/"
}
{
"@context": {
"firstname": "http://schema.org/givenName",
"lastname": "http://schema.org/familyName",
"homepage": "http://schema.org/url"
},
"id": "markus",
"firstname": "Markus",
"lastname": "Lanthaler",
"homepage": "http://www.markus-lanthaler.com/"
}
{
"@context": {
"firstname": "http://schema.org/givenName",
"lastname": "http://schema.org/familyName",
"homepage": "http://schema.org/url"
},
"@id": "/people/markus",
"firstname": "Markus",
"lastname": "Lanthaler",
"homepage": "http://www.markus-lanthaler.com/"
}
{
"@context": {
"firstname": "http://schema.org/givenName",
"lastname": "http://schema.org/familyName",
"homepage": "http://schema.org/url"
},
"@id": "/people/markus",
"firstname": "Markus",
"lastname": "Lanthaler",
"homepage": "http://www.markus-lanthaler.com/"
}
{
"@context": {
"firstname": "http://schema.org/givenName",
"lastname": "http://schema.org/familyName",
"homepage": "http://schema.org/url"
},
"@id": "/people/markus",
"firstname": "Markus",
"lastname": "Lanthaler",
"homepage": { "@id": "http://www.markus-lanthaler.com/" }
}
{
"@context": {
"firstname": "http://schema.org/givenName",
"lastname": "http://schema.org/familyName",
"homepage": {
"@id": "http://schema.org/url", "@type": "@id" },
},
"@id": "/people/markus",
"firstname": "Markus",
"lastname": "Lanthaler",
"homepage": "http://www.markus-lanthaler.com/"
}
{
"@context": {
"firstname": "http://schema.org/givenName",
"lastname": "http://schema.org/familyName",
"homepage": {
"@id": "http://schema.org/url", "@type": "@id" }
},
"@id": "/people/markus",
"@type": "http://schema.org/Person",
"firstname": "Markus",
"lastname": "Lanthaler",
"homepage": "http://www.markus-lanthaler.com/"
}
{
"@context": {
"firstname": "http://schema.org/givenName",
"lastname": "http://schema.org/familyName",
"homepage": {
"@id": "http://schema.org/url", "@type": "@id" }
},
"@id": "/people/markus",
"@type": "http://schema.org/Person",
"firstname": "Markus",
"lastname": "Lanthaler",
"homepage": "http://www.markus-lanthaler.com/"
}
{
"@context": "/contexts/person.jsonld",
"@id": "/people/markus",
"@type": "http://schema.org/Person",
"firstname": "Markus",
"lastname": "Lanthaler",
"homepage": "http://www.markus-lanthaler.com/"
}
CMF
{
"id": "cso29ax",
"title": "Symfony Live Portland 2013",
"description": "Prepare slides",
"is_open": true,
"created_at": "2012-11-26T04:49:44Z",
"comments": "/issues/cso29ax/comments/"
}
http://example.com/issues/cso29ax
{
"@context": "/ctx/context.jsonld",
"id": "cso29ax",
"title": "Symfony Live Portland 2013",
"description": "Prepare slides",
"is_open": true,
"created_at": "2012-11-26T04:49:44Z",
"comments": "/issues/cso29ax/comments/"
}
http://example.com/issues/cso29ax
{
"@context": "/ctx/context.jsonld",
"id": "cso29ax",
"title": "Symfony Live Portland 2013",
"description": "Prepare slides",
"is_open": true,
"created_at": "2012-11-26T04:49:44Z",
"comments": "/issues/cso29ax/comments/"
}
http://example.com/issues/cso29ax
{
"@id": "#comments",
"@type": "hydra:Link",
"supportedOperations": [
{
"@id": "#create-comment",
"@type": "hydra:CreateResourceOperation",
"label": "Creates a new comment",
"method": "POST",
"expects": "#Comment",
"returns": "#Comment"
}
]
}
http://example.com/api/doc
{
"@id": "#Comment",
"@type": "hydra:Class",
"supportedProperties": [
{
"property": "#text",
"required": true,
"readonly": false,
"writeonly": false
}
]
}
http://example.com/api/doc
/**
* An Issue tracked by the system.
*
* @HydraExpose()
*/
class Issue
{
/**
* The comments associated with this issue
*
* @HydraExpose()
* @HydraCollection("issue_comments")
* @HydraOperations("issue_comment_create")
*/
private $comments;
// ... other members and methods ...
}
$ php app/console hydra:generate:crud
--entity=MLDemoBundle:Issue
--route-prefix=/issues/
--with-write
--no-interaction
CRUD generation
Generating the CRUD code: OK
You can now start using the generated code!
/**
* Issue controller
*
* @Route("/issues")
*/
class IssueController extends HydraController
{
/**
* Creates a new Issue
*
* @Route("/", name="issue_create")
* @Method("POST")
*
* @HydraOperation(expect = "MLDemoBundleEntityIssue")
*
* @return MLDemoBundleEntityIssue
*/
public function collectionPostAction(Request $request)
{
...
Hydra Console
© 2013, Markus Lanthaler. Some Rights Reserved.
http://creativecommons.org/licenses/by-nc-sa/3.0/
Thank You
Questions?
Markus Lanthaler
http://www.markus-lanthaler.com
@MarkusLanthaler
mail@markus-lanthaler.com
Image Credits
(1) http://www.flickr.com/photos/justinwkern/3729649672/
(2) http://www.flickr.com/photos/alexdram/3095419858/
(3) http://www.flickr.com/photos/kaptainkobold/3203311346/
(11) http://info.cern.ch/hypertext/WWW/TheProject.html
(15) Adapted from http://www.flickr.com/photos/nebarnix/361650027/
(16) http://www.flickr.com/photos/joyoflife/1570126182/
(19) http://www.flickr.com/photos/rossiprojects/5592552858/
(21) http://www.flickr.com/photos/rossiprojects/5592552858/
(23) http://www.flickr.com/photos/clevercupcakes/4397152402/
(31) http://schema.org/Person
(36) http://www.vonwong.com/
(42) http://www.flickr.com/photos/jakecaptive/3205277810/
(47) http://www.flickr.com/photos/sis/126152933/

Building Next-Generation Web APIs with JSON-LD and Hydra

  • 1.
    Building Next-Gen WebAPIs with JSON-LD and Hydra Markus Lanthaler
  • 2.
    Why do we needa website? Of course we have a website Why do we need an API? 1995 2000 2005 2010 Of course we have an API Adapted from T. Vitvar’s and J. Musser’s ECOWS 2010 Keynote, “ProgrammableWeb.com:Statistics, Trends, and Best Practices”
  • 4.
    Using Web APIsis still challenging
  • 6.
    Level 0:The Swampof POX Level 1: Resources Level 2: HTTPVerbs
  • 8.
    { "id": "cso29ax", "title": "SymfonyLive Portland 2013", "description": "Prepare slides", "is_open": true, "created_at": "2012-11-26T04:49:44Z" } http://example.com/issues/cso29ax
  • 9.
  • 10.
    { "id": "cso29ax", "title": "SymfonyLive Portland 2013", "description": "Prepare slides", "is_open": true, "created_at": "2012-11-26T04:49:44Z" } http://example.com/issues/cso29ax http://example.com/issue/{id}/comments/
  • 12.
    { "id": "cso29ax", "title": "SymfonyLive Portland 2013", "description": "Prepare slides", "is_open": true, "created_at": "2012-11-26T04:49:44Z", "comments": "/issues/cso29ax/comments/" } http://example.com/issues/cso29ax
  • 13.
    Level 0:The Swampof POX Level 1: Resources Level 2: HTTPVerbs Level 3: Hypermedia Controls
  • 14.
    { "id": "cso29ax", "title": "SymfonyLive Portland 2013", "description": "Prepare slides", "is_open": true, "created_at": "2012-11-26T04:49:44Z", "comments": "/issues/cso29ax/comments/" } http://example.com/issues/cso29ax
  • 15.
    Every API isa snowflake
  • 16.
    Result: tightly coupled& brittle systems
  • 17.
    { "id": "cso29ax", "title": "SymfonyLive Portland 2013", "description": "Prepare slides", "is_open": true, "created_at": "2012-11-26T04:49:44Z", "comments": "/issues/cso29ax/comments/" } http://example.com/issues/cso29ax
  • 18.
    { 69 64: 6373 6f 32 39 61 78 74 69 74 6c 65: 53 79 6d 66 6f 6e 79 20 4c 69 76 … 64 65 73 63 72 69 70 74 69 6f 6e: 50 72 65 70 61 … 69 73 5f 6f 70 65 6e: 01 63 72 65 61 74 65 64 5f 61 74: 32 30 31 32 2d 31 … 63 6f 6d 6d 65 6e 74 73: 2f 69 73 73 75 65 73 2f … } http://example.com/issues/cso29ax
  • 19.
  • 20.
    Linked Data Principles TimBerners-Lee, 2006
  • 21.
  • 23.
    Make data self-descriptiveby mapping concepts to URLs
  • 24.
    { "id": "markus", "firstname": "Markus", "lastname":"Lanthaler", "homepage": "http://www.markus-lanthaler.com/" }
  • 25.
    { "@context": { "firstname": "http://schema.org/givenName", "lastname":"http://schema.org/familyName", "homepage": "http://schema.org/url" }, "id": "markus", "firstname": "Markus", "lastname": "Lanthaler", "homepage": "http://www.markus-lanthaler.com/" }
  • 26.
    { "@context": { "firstname": "http://schema.org/givenName", "lastname":"http://schema.org/familyName", "homepage": "http://schema.org/url" }, "@id": "/people/markus", "firstname": "Markus", "lastname": "Lanthaler", "homepage": "http://www.markus-lanthaler.com/" }
  • 27.
    { "@context": { "firstname": "http://schema.org/givenName", "lastname":"http://schema.org/familyName", "homepage": "http://schema.org/url" }, "@id": "/people/markus", "firstname": "Markus", "lastname": "Lanthaler", "homepage": "http://www.markus-lanthaler.com/" }
  • 28.
    { "@context": { "firstname": "http://schema.org/givenName", "lastname":"http://schema.org/familyName", "homepage": "http://schema.org/url" }, "@id": "/people/markus", "firstname": "Markus", "lastname": "Lanthaler", "homepage": { "@id": "http://www.markus-lanthaler.com/" } }
  • 29.
    { "@context": { "firstname": "http://schema.org/givenName", "lastname":"http://schema.org/familyName", "homepage": { "@id": "http://schema.org/url", "@type": "@id" }, }, "@id": "/people/markus", "firstname": "Markus", "lastname": "Lanthaler", "homepage": "http://www.markus-lanthaler.com/" }
  • 30.
    { "@context": { "firstname": "http://schema.org/givenName", "lastname":"http://schema.org/familyName", "homepage": { "@id": "http://schema.org/url", "@type": "@id" } }, "@id": "/people/markus", "@type": "http://schema.org/Person", "firstname": "Markus", "lastname": "Lanthaler", "homepage": "http://www.markus-lanthaler.com/" }
  • 32.
    { "@context": { "firstname": "http://schema.org/givenName", "lastname":"http://schema.org/familyName", "homepage": { "@id": "http://schema.org/url", "@type": "@id" } }, "@id": "/people/markus", "@type": "http://schema.org/Person", "firstname": "Markus", "lastname": "Lanthaler", "homepage": "http://www.markus-lanthaler.com/" }
  • 33.
    { "@context": "/contexts/person.jsonld", "@id": "/people/markus", "@type":"http://schema.org/Person", "firstname": "Markus", "lastname": "Lanthaler", "homepage": "http://www.markus-lanthaler.com/" }
  • 34.
  • 37.
    { "id": "cso29ax", "title": "SymfonyLive Portland 2013", "description": "Prepare slides", "is_open": true, "created_at": "2012-11-26T04:49:44Z", "comments": "/issues/cso29ax/comments/" } http://example.com/issues/cso29ax
  • 38.
    { "@context": "/ctx/context.jsonld", "id": "cso29ax", "title":"Symfony Live Portland 2013", "description": "Prepare slides", "is_open": true, "created_at": "2012-11-26T04:49:44Z", "comments": "/issues/cso29ax/comments/" } http://example.com/issues/cso29ax
  • 39.
    { "@context": "/ctx/context.jsonld", "id": "cso29ax", "title":"Symfony Live Portland 2013", "description": "Prepare slides", "is_open": true, "created_at": "2012-11-26T04:49:44Z", "comments": "/issues/cso29ax/comments/" } http://example.com/issues/cso29ax
  • 40.
    { "@id": "#comments", "@type": "hydra:Link", "supportedOperations":[ { "@id": "#create-comment", "@type": "hydra:CreateResourceOperation", "label": "Creates a new comment", "method": "POST", "expects": "#Comment", "returns": "#Comment" } ] } http://example.com/api/doc
  • 41.
    { "@id": "#Comment", "@type": "hydra:Class", "supportedProperties":[ { "property": "#text", "required": true, "readonly": false, "writeonly": false } ] } http://example.com/api/doc
  • 44.
    /** * An Issuetracked by the system. * * @HydraExpose() */ class Issue { /** * The comments associated with this issue * * @HydraExpose() * @HydraCollection("issue_comments") * @HydraOperations("issue_comment_create") */ private $comments; // ... other members and methods ... }
  • 45.
    $ php app/consolehydra:generate:crud --entity=MLDemoBundle:Issue --route-prefix=/issues/ --with-write --no-interaction CRUD generation Generating the CRUD code: OK You can now start using the generated code!
  • 46.
    /** * Issue controller * *@Route("/issues") */ class IssueController extends HydraController { /** * Creates a new Issue * * @Route("/", name="issue_create") * @Method("POST") * * @HydraOperation(expect = "MLDemoBundleEntityIssue") * * @return MLDemoBundleEntityIssue */ public function collectionPostAction(Request $request) { ...
  • 49.
  • 54.
    © 2013, MarkusLanthaler. Some Rights Reserved. http://creativecommons.org/licenses/by-nc-sa/3.0/ Thank You
  • 55.
  • 56.
    Image Credits (1) http://www.flickr.com/photos/justinwkern/3729649672/ (2)http://www.flickr.com/photos/alexdram/3095419858/ (3) http://www.flickr.com/photos/kaptainkobold/3203311346/ (11) http://info.cern.ch/hypertext/WWW/TheProject.html (15) Adapted from http://www.flickr.com/photos/nebarnix/361650027/ (16) http://www.flickr.com/photos/joyoflife/1570126182/ (19) http://www.flickr.com/photos/rossiprojects/5592552858/ (21) http://www.flickr.com/photos/rossiprojects/5592552858/ (23) http://www.flickr.com/photos/clevercupcakes/4397152402/ (31) http://schema.org/Person (36) http://www.vonwong.com/ (42) http://www.flickr.com/photos/jakecaptive/3205277810/ (47) http://www.flickr.com/photos/sis/126152933/