REST More with JSON-API and Fractal

REST More with JSON-API and Fractal

How we can use open standards and specifications to design and build RESTful APIs that are easier to developer, maintain and use. Focusing on JSON-API (http://jsonapi.org/) and the Fractal php package (http://fractal.thephpleague.com/)

Given @ PHP Varna User Group, May 2017

807dcecdfca0e4078d89127cd440c039?s=128

Boyan Yordanov

May 18, 2017
Tweet

Transcript

  1. REST MORE WITH JSON:API AND FRACTAL Use open standards to

    build better and more maintainable APIs 1
  2. $> WHOAMI $> BOYAN Developer at Shtrak Community cheerleader Wannabe

    speaker and writer Partialy-guilty for phpVarna 2 . 1
  3. WHERE TO FIND ME @specter_bg boyanyordanov boyanyordanov 2 . 2

  4. WHAT IS REST ? REpresentational State Transfer 3 . 1

  5. WHAT IS REST ? pretty URLs ? HTTP verbs for

    actions ? None of those ? 3 . 2
  6. ACHIEVING ETERNAL GLORY Richardson Maturity Model post by Martin Fowler

    3 . 3
  7. SO ARE WE ... ? ... probably NOT. But does

    it really matter? 3 . 4
  8. OPEN STANDARDS What makes an open standard ? Cooperation Adherence

    to Principles Due process, Broad concensus, Transparency, Balance, Openness Collective Empowerment Availability Voluntary Adoption 4 . 1
  9. EXAMPLES WE USE EVERYDAY HTTP CSS, HTML, ECMAScript C, C++,

    Java ( ), C# PHP RFC process and PHP-Fig PSRs (sort of) JCP 4 . 2
  10. OPEN STANDARDS AND APIS Auth and security Oauth 1/2, OpenID

    Connect, JWT Documentation / helper openapi/swagger, api blueprint, RAML json- schema Representation / content types HAL, Problem JSON, Hydra and json-ld, JSON- API 5
  11. BENEFITS OF USING STANDARDS FOR APIS easier to design and

    build easier to maintain easier to consume easier to test 6 . 1
  12. 6 . 2

  13. 7

  14. OVERVIEW well-de ned conventions for listing, creating, updating and deleting

    resources recommendations for details not covered in the main spec server and client libraries in most popular languages 8
  15. 9

  16. { "links": { "self": "/meetups/1" }, "data": { "type": "articles",

    "id": "1", "attributes": { "title": "JSON API paints my bikeshed!" }, "relationships": { "author": { "links": {"self": "...", "related": "..."}, "data": { "type": "people", "id": "9" } } } } } 10
  17. THE MEDIA TYPE Client call Server response Accept: application/vnd.api+json Content-Type:

    application/vnd.api+json Content-type: application/vnd.api+json 11 . 1
  18. DECONSTRUCTING THE COMPOUND DOCUMENT TOP LEVEL ATTRIBUTES data errors meta

    links jsonapi included 11 . 2
  19. TYPES OF OBJECTS resource object link object resource identi er

    object error object 12 . 1
  20. THE RESOURCE OBJECT { "type": "meetups", "id": "1", "attributes": {

    "name": "PHP Varna", "description": "The php UG of Varna", "next-meetup": "2017-05-18T19:30:00.200Z" } } 12 . 2
  21. LINKS OBJECT "links": { "self": "/meetups/1" } ... "links": {

    "related": { "href": "/meetups/1/attendees", "meta": { "count": 50 } } } 12 . 3
  22. RESOURCE IDENTIFIER OBJECT { "id": "1", "type": "meetups", "meta": {

    "attendee-count": 50 } } 12 . 4
  23. RELATIONSHIPS OBJECT "relationships": { "location": { "links": { "self": "meetups/1/relationships/venue",

    "related": "meetups/1/venue" }, "data": { "type": "venues", "id": "1" } } } 12 . 5
  24. PUTTING IT ALL TOGETHER GET /meetups HTTP/1.1 Accept: application/vnd.api+json {

    "data": { "type": "meetups", "id": "1", "attributes": { "name": "PHP Varna", "description": "The php UG of Varna", "next-meetup": "2017-05-18T19:30:00.200Z" }, "relationships": { "venue": { "data": { "type": "venues", "id": "1" } } } } } 13
  25. RELATIONSHIPS Change the meetup place PATCH /meetups/1/relationships/venue HTTP/1.1 Content-Type: application/vnd.api+json

    Accept: application/vnd.api+json { "data": { "type": "venues", "id": "12" } } 14 . 1
  26. Remove the venue (untill we nd a new one?) PATCH

    /meetups/1/relationships/venue HTTP/1.1 Content-Type: application/vnd.api+json Accept: application/vnd.api+json { "data": null } 14 . 2
  27. SIDELOADING RELATIONSHIPS (GET MEETUP AND VENUE) GET /meetups/1?include=venue HTTP/1.1 HTTP/1.1

    200 OK Content-Type: application/vnd.api+json { "data": { // resource object from previous slides }, "included": [ { "type": "venues", "id": "1", "attributes": { "name": "VarnaLab", "address": "ul. 'Pencho Slaveykov' 50" } } ] 14 . 3
  28. ERROR OBJECTS HTTP/1.1 422 Unprocessable Entity Content-Type: application/vnd.api+json { "errors":

    [ { "status": "422", "source": { "pointer": "/data/attributes/name" }, "title": "Invalid Attribute", "detail": "Meetup name must contain at least three characters." } ] } 15
  29. QUERY PARAMS ltering sorting GET /meetups?filter[venue]="VarnaLab" HTTP/1.1 Accept: application/vnd.api+json GET

    /meetups?sort=name HTTP/1.1 Accept: application/vnd.api+json GET /meetups?sort=-name HTTP/1.1 Accept: application/vnd.api+json 16 . 1
  30. sparse eldsets GET /meetups?fields[meetups]=name HTTP/1.1 Accept: application/vnd.api+json { "data": [

    { "id": "1", "type": "meetups", "attributes": { "name": "PHP Varna" } }, { "id": "2", "type": "meetups", "attributes": { "name": "WordPress Varna" } } 16 . 2
  31. PAGINATION GET /meetups?page[number]=3&page[size]=1 HTTP/1.1 Accept: application/vnd.api+json 16 . 3

  32. { "meta": { "total-pages": 12 }, "data": [ { "type":

    "meetups", "id": "2", "attributes": { "name": "WordPress Varna", "description": "The place for WordPress entusiasts", "next-meetup": "", } } ], "links": { "self": "/meetups?page[number]=2&page[size]=1", "first": "/meetups?page[number]=1&page[size]=1", "prev": "/meetups?page[number]=2&page[size]=1", "next": "/meetups?page[number]=4&page[size]=1", "last": "/meetups?page[number]=12&page[size]=1" } } 16 . 4
  33. RESOURCE OPERATIONS listing show 404 examples get individual resource create

    show validation errors examples update delete 17
  34. CREATE A MEETUP POST /meetups HTTP/1.1 Content-Type: application/vnd.api+json Accept: application/vnd.api+json

    { "data": { "type": "meetups", "attributes": { "name": "JS Varna?", "desription": "Does Varna need another meetup?" }, "relationships": {//optional} } } 18
  35. FRACTAL Like a view layer for your JSON/YAML/etc. 19

  36. MAIN CONCEPTS Fractal Manager Resource Includes Serializer Transformer Pagination via

    paginator via cursor 20
  37. RESOURCES & TRANSFORMERS Closure transformer use League\Fractal\Resource\Item; $meetup = retrieveFromDB();

    $meetupResource = new Item($meetup, function($m) { return [ 'id' => $meetup->id, 'name' => $meetup->name, 'description' => $meetup->description, 'next-meetup' => $meetup->events->getNext()->date ]; }); 21 . 1
  38. Class transformer use League\Fractal\TransformerAbstract; use League\Fractal\Resource\Collection; class MeetupTransformer extends TransformerAbstract

    { public function transform(Meetup $meetup) { return [ 'id' => $meetup->id, 'name' => $meetup->name, 'description' => $meetup->description, 'next-meetup' => $meetup->events->getNext()->date ]; } } $data = new Collection($meetups, new MeetupTransformer); 21 . 2
  39. De ning includes use League\Fractal\TransformerAbstract; use League\Fractal\Resource\Collection; class MeetupTransformer extends

    TransformerAbstract { protected $availableIncludes = ['venues']; public function transform($meetup) { ... } public function includeVenue($meetup) { $venue = $meetup->venue; $transformer = new VenueTransformer; return $this->item($venue, $transformer, 'venues'); } } $data = new Collection($meetups, new MeetupTransformer); 21 . 3
  40. Creating a resource use League\Fractal\Serializer\JsonApiSerializer; $manager = new League\Fractal\Manager(); $baseUrl

    = 'http://meetup.dev'; $manager->setSerializer(new JsonApiSerializer($baseUrl)); // ORM call $meetup = Meetup::find(1); // Make a resource out of the data and $resource = new Item($meetup, new MeetupTransformer, 'meetups'); // Run all transformers $manager->createData($resource)->toArray(); 21 . 4
  41. Result [ "data" => [ "type" => "meetups", "id" =>

    "1", "attributes" => [ "name" => "PHP Varna", "description" => "The php UG in Varna", "next-meetup" => "2017-05-18T19:30:00.200Z" ] ], "links" => [ "self" => "http://meetup.dev/meetups/1" ] ] 21 . 5
  42. Using includes use League\Fractal\Serializer\JsonApiSerializer; $manager = new League\Fractal\Manager(); $baseUrl =

    'http://meetup.dev'; $manager->setSerializer(new JsonApiSerializer($baseUrl)); // ORM call $meetup = Meetup::find(1); if (isset($_GET['include'])) { $fractal->parseIncludes($_GET['include']); } 21 . 6
  43. WHAT IS MISSING FROM THE SPEC? ltering sorting sparse eldsets

    pagination - present and usable but not spec compliant 22 . 1
  44. PAGINATION use League\Fractal\Resource\Collection; use League\Fractal\Pagination\IlluminatePaginatorAdapter; use Acme\Model\Book; use Acme\Transformer\BookTransformer; $paginator

    = Meetup::paginate(); $meetups = $paginator->getCollection(); $resource = new Collection($meetups, new MeetupTransformer); $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); 22 . 2
  45. SUPPORTED ADAPTERS Laravel Doctrine Zend Framework Phalcon Pagerfanta your own

    PaginatorInterface implementation 22 . 3
  46. WHAT IS NOT INCLUDED ON PURPOSE? content negotiation response code

    handling error objects 22 . 4
  47. HANDLING ERROR OBJECTS 23

  48. Use a base class class JsonApiError extends \Exception { protected

    $status; protected $title; protected $detail; protected $source; protected $meta; public function toArray() {...} } 24 . 1
  49. Extend it for speci c errors class NotFoundError extends JsonApiError

    { protected $status = 404; protected $title = 'Resource not found'; } 24 . 2
  50. EASIER FRACTAL composer install spatie/fractalistic A developer friendly wrapper around

    Fractal 25 . 1
  51. Fractal::create() ->collection($meetups) ->transformWith(new MeetupTransformer()) ->includeVenue() ->toArray(); 25 . 2

  52. ALTERNATIVES neomerx/json-api resource transformation query parameters handling errors 26

  53. RESOURCES http://jsonapi.org/ http://fractal.thephpleague.com/ https://www.ics.uci.edu/~ elding/pubs/dissertation Steve Klabnik's talk: past, present

    and future of json https://youtu.be/Foi54om6oGQ discussion: who's using json-api - https://github.com api/issues/825 27
  54. THANK YOU 28

  55. QUESTIONS ? 29