Practical APIs

Practical APIs

Let's face it: with emerging publish channels like Google AMP or Facebook Instant Articles, it's not enough to just deliver a HTML representation of your content anymore. In this workshop you'll see why you need an API, what the benefits are, and at what you should aim when designing an API. We will extend the eZ Platforms REST API, look at its advantages and disadvantages and discuss other options.

F1a81e1cfca792b9754e2489a8fed53f?s=128

Urban Etter

August 31, 2016
Tweet

Transcript

  1. PRACTICAL API

  2. WELCOME

  3. API4EZ

  4. URBAN ETTER @URBMC ON TWITTER @URBAN ON EZ COMMUNITY SLACK

  5. None
  6. 4 PARTS

  7. WHAT IS AN API

  8. WHY DO I NEED AN API

  9. HOW DO I CREATE AN API

  10. EZ RICH TEXT

  11. EXERCISES

  12. EXERCISES > README.md > What to do > Bonus >

    Hints > Code
  13. ABOUT INDIAN PALE ALE

  14. INDIAN PALE ALE > High hop content > Tends to

    have more volume percent > Said to be brewed for long ship journeys to India
  15. IPA

  16. IPA-API

  17. ABOUT BREW DOG

  18. PUNK IPA

  19. DEAD PONY CLUB (3.8%)

  20. SIGGIBRÄU

  21. WHAT IS AN API

  22. TWITTER API GET https://api.twitter.com/1.1/statuses/show.json? id=21046285714025267

  23. EZ PLATFORM REST API { "Content": { "_media-type": "application/vnd.ez.api.ContentInfo+json", "_href":

    "/api/ezp/v2/content/objects/86", "_remoteId": "73be2a5122ecd6a4a7ad1cef6b0393f1", "_id": 86, "ContentType": { "_media-type": "application/vnd.ez.api.ContentType+json", "_href": "/api/ezp/v2/content/types/18" }, "Name": "Build a better performing site with continuous optimization", "Versions": { "_media-type": "application/vnd.ez.api.VersionList+json", "_href": "/api/ezp/v2/content/objects/86/versions" }, "CurrentVersion": { "_media-type": "application/vnd.ez.api.Version+json", "_href": "/api/ezp/v2/content/objects/86/currentversion" } } }
  24. PHP API $contentInfo = $contentService->loadContentInfo($contentId); echo $contentInfo->mainLocationId;

  25. PHP API $myKitten = new Cat('Kitty'); $myKitten->lookCute();

  26. SQL SELECT id, name, review FROM ipa WHERE id =

    57;
  27. BASH API ln -s my/source my/target

  28. APPLICATION PROGRAMMING INTERFACE

  29. INTERFACE TO A PROGRAMM

  30. PROGRAMMABLE

  31. BUILDING BLOCKS A good API makes it easier to develop

    a program by providing all the building blocks, which are then put together by the programmer. — Wikipedia
  32. SEPARATES INTERFACE AND IMPLEMENTATION

  33. SPECIFICATION VS IMPLEMENTATION

  34. WHAT VS HOW

  35. AN API ONLY SPECIFIES WHAT

  36. PHP REPRESENTATIONS > interface: specification > trait: implementation > class:

    specification + implementation
  37. WHO WROTE A CLASS?

  38. WE'RE DONE, RIGHT?

  39. INTRODUCING DEMO CONTENT

  40. None
  41. None
  42. CALL TO ACTION EXERCISE 1

  43. WHY DO YOU NEED AN API

  44. IF YOU'RE BUILDING A SLACK BOT

  45. WHY YOU NEED AN API AS A PUBLISHER

  46. None
  47. None
  48. None
  49. None
  50. None
  51. None
  52. DIFFERENT REPRESENTATIONS OF YOUR CONTENT

  53. PROGRAMMABLE ACCESS TO YOUR CONTENT

  54. CONTENT DELIVERY

  55. REMEMBERING BUILDING BLOCKS A good API makes it easier to

    develop a program by providing all the building blocks, which are then put together by the programmer. — Wikipedia
  56. BUZZWORD ALARM!

  57. API FIRST

  58. API AS A PRODUCT

  59. HEADLESS CMS

  60. BASICALLY THE SAME

  61. PROGRAMMABLE ACCESS TO YOUR CONTENT

  62. EZ REST API TO DELIVER CONTENT

  63. WE'RE DONE, RIGHT?

  64. REMEMBER > API specifies What not How > Programmable content

    delivery
  65. My content is an ARTICLE not an EZ CONTENT OBJECT

  66. LAYERED APIS

  67. MOBILE APP USECASE > Request count matters > Verbosity of

    response matters
  68. HIGH LEVEL API/ DOMAIN API [ { "name": "Punk IPA",

    "review": 5, "brewery": "Brew Dog", "image": "http://www.ipaapi.io/images/punk-ipa.jpg", }, { "name": "Dead Pony Club", "review": 4.5, "brewery": "Brew Dog", "image": "http://www.ipaapi.io/images/dead-pony-club.jpg" } ]
  69. HIGH LEVEL API > Specified for a certain use case

    > No need to know internals > Not flexible > Needed info with one request > Only one format needs to be supported
  70. LOW LEVEL API { "Content": { "_media-type": "application/vnd.ez.api.ContentInfo+json", "_href": "/api/ezp/v2/content/objects/86",

    "_remoteId": "73be2a5122ecd6a4a7ad1cef6b0393f1", "_id": 86, "ContentType": { "_media-type": "application/vnd.ez.api.ContentType+json", "_href": "/api/ezp/v2/content/types/18" }, "Name": "Build a better performing site with continuous optimization", "Versions": { "_media-type": "application/vnd.ez.api.VersionList+json", "_href": "/api/ezp/v2/content/objects/86/versions" }, "CurrentVersion": { "_media-type": "application/vnd.ez.api.Version+json", "_href": "/api/ezp/v2/content/objects/86/currentversion" } } }
  71. LOW LEVEL API > One API for loads of use

    cases > Knowledge of some internals may be required > Very flexible > Different formats (XML or JSON)
  72. LOW LEVEL API ✅

  73. LET'S BUILD A DOMAIN API

  74. HOW TO BUILD AN API

  75. MY CONTENT IS AN ARTICLE AN IPA

  76. PHP ENTITY (VALUE OBJECT) class IPA { public $id; public

    $name; public $review; }
  77. ENTITY > Value objects > Service which "loads" value objects

    > Public attributes > eZ Publish Public API has the same architecture
  78. CREATE PHP ENTITIES AS A SERVICE class IpaService { public

    function loadIpa($contentId) { $content = $contentService->loadContent($contentId); $ipa = new IPA(); $ipa->name = $content->getFieldValue('title')->text; // ... } }
  79. CALL TO ACTION EXERCISE 2

  80. NOW WE HAVE A NORMALIZED ENTITY

  81. CONVERT ENTITY TO NEEDED REPRESENTATION

  82. WE'RE DONE, RIGHT?

  83. WHAT ABOUT IMAGES

  84. NOT POSSIBLE TO SEND ALL DATA IN JSON

  85. => GIVE URL WHERE ADDITIONAL CONTENT CAN BE LOADED

  86. IMAGES [ { "name": "Punk IPA", "review": 5, "image": "http://www.ipaapi.io/images/punk-ipa.jpg"

    }, { "name": "Dead Pony Club", "review": 4.5, "image": "http://www.ipaapi.io/images/dead-pony-club.jpg" } ]
  87. CALL TO ACTION EXERCISE 3

  88. REQUIREMENT > XML and JSON > Relationship between IPA and

    Brewery not clear
  89. EZ REST API COMPONENT > Rest Controller > Rest Routing

    > Visitor pattern
  90. VISITORS > Main target: Translate object > Visits objects of

    a graph, therefore a Visitor > Register as visitor with Symfony DIC tag
  91. EZ REST VISITOR - BASIC public function visit(Visitor $visitor, Generator

    $generator, $data) { $generator->startObjectElement('ipa'); $generator->startValueElement('name', $data->name); $generator->endValueElement('name'); $generator->startValueElement('rating', $data->review); $generator->endValueElement('rating'); $generator->endObjectElement('ipa'); }
  92. EZ REST VISITOR - MIME && HREF public function visit(Visitor

    $visitor, Generator $generator, $data) { $mediaType = 'ipa'; $generator->startObjectElement('ipa', $mediaType); $visitor->setHeader('Content-Type', $generator->getMediaType($mediaType)); $generator->startAttribute( 'href', $this->router->generate('ezpublish_rest_ipa', array('contentId' => $data->id)) ); $generator->endAttribute('href'); // ... $generator->endObjectElement('ipa'); }
  93. EZ REST CONTROLLER public function ipaAction($contentId) { $service = $this->container->get('app.ipa_service');

    $ipa = $service->loadIpa($contentId); return $ipa; }
  94. EZ REST CONTROLLER: CACHEDVALUE public function ipaAction($contentId) { $service =

    $this->container->get('app.ipa_service'); $ipa = $service->loadIpa($contentId); $contentService = $this->container->get('ezpublish.api.service.content'); $locationId = $contentService->loadContentInfo($contentId)->mainLocationId; $cached = new CachedValue($ipa, ['locationId' => $locationId]); return $cached; }
  95. CALL TO ACTION EXERCISE 4

  96. ADVANTAGES OF EZ REST API > Support of Mime Types

    > XML or JSON format > Caching > Visitor pattern
  97. DISADVANTAGES OF EZ REST API > Some HTTP knowlege (MIME

    types, headers) needed > Given URL prefix (at least when using legacy) > Verbose visitors
  98. EZ RICH TEXT EZ XML TEXT

  99. POWERFUL FEATURE OF EZ PLATFORM

  100. EZ PUBLISH: EZXMLTEXT EZ PLATFORM: EZRICHTEXT

  101. EZ RICH TEXT INTERNAL FORMAT <?xml version="1.0" encoding="UTF-8"?> <section xmlns="namespace

    stuff"> <para> Probably my favorite IPA. Some citrus and grapefruit notes. And just.... awsome. </para> <ezembed xlink:href="ezcontent://64" view="embed" ezxhtml:class="ez-embed-type-image"> <ezconfig><ezvalue key="size">small</ezvalue></ezconfig> </ezembed> </section>
  102. EZEMBED > means there can be "subviews" which get rendered

    independently > => override of templates !
  103. EZEMBED HARD TO SERIALIZE => XML EVEN IN JSON REPRESENTATION

  104. PROBLEM > A JSON representation contains <ezembed> XML tags >

    => How, not What
  105. REPRESENTATIONS RENDERED BY EZ PLATFORM/ EZ PUBLISH

  106. PROVIDE LINKS TO REPRESENTATIONS [ { "name": "Punk IPA", "review":

    5, "brewery": "Brew Dog", "image": "http://www.ipaapi.io/images/punk-ipa.jpg", "representations": { "html": "http://www.ipaapi.io/ipa/57", "google_amp": "http://www.ipaapi.io/amp/57" } } ]
  107. PROBLEMS TO SOLVE > Different representations, different templates > Representations

    which do not support all content type embeds
  108. EZ PLATFORM OVERRIDES CUSTOM REPRESENTATION MATCHER

  109. CUSTOM MATCHER content_view: embed: amp_image: template: "AppBundle:Amp:image.html.twig" match: Identifier\ContentType: "image"

    app.matcher.representation: "amp"
  110. CUSTOM MATCHER: CONTROLLER public function ipaAmpAction(Request $request, $contentId) { $ipa

    = $this->get('app.ipa_service')->getIpa($contentId); $this->get('app.matcher.representation')->setRepresentation('amp') $response = $this->render( 'AppBundle:Amp:ipa.html.twig', [ 'content' => $ipa, ] ); return $response; }
  111. CUSTOM MATCHER: MATCHER class Representation extends MultipleValued { private $representationName;

    public function setRepresentationName($name) { $this->representationName = $name; } public function match(View $view) { return isset($this->values[$this->representationName]); } }
  112. RICHTEXT WITH NORMAL IMAGES

  113. RICHTEXT WITH OVERRIDDEN IMAGES

  114. CALL TO ACTION EXERCISE 5

  115. UNSOLVED PROBLEM > New embeddable content type means new override

    rule for every representation > Lot of empty templates for a new representation
  116. EZRICHTEXT OUTPUTS > Different output: xhtml5, xhtml5_edit > Renderer used

    for different outputs
  117. USE CUSTOM RENDERER TO CLEAN OUT UNSUPPORTED TYPES

  118. RICHTEXT RENDERER > Priority 0: Link Renderer > Priority 10:

    Embed Renderer > New Priority 5: Embed Reducer
  119. RICHTEXTREDUCER class RichTextReducer extends Render implements Converter { /** *

    Converts given $xmlDoc into another \DOMDocument object. */ public function convert(DOMDocument $xmlDoc) { $embeds = $xmlDoc->getElementsByTagName('ezembed'); foreach ($embeds as $embed) { $href = $embed->getAttribute('xlink:href'); if (!$this->representation->supportsEmbed($href)) { $embed->parentNode->removeChild($embed); } } return $xmlDoc; } }
  120. REGISTER RICHTEXTREDUCER app.richtext.converter.reducer: class: "AppBundle\Service\RichTextReducer" arguments: - "@ezpublish.fieldType.ezrichtext.renderer" tags: -

    {name: ezpublish.ezrichtext.converter.output.xhtml5, priority: 5}
  121. CALL TO ACTION EXERCISE 6

  122. TANK YOU @URBMC ON TWITTER @URBAN ON EZ COMMUNITY SLACK