Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

Boyan Yordanov

May 18, 2017
Tweet

More Decks by Boyan Yordanov

Other Decks in Programming

Transcript

  1. REST MORE WITH JSON:API AND
    FRACTAL
    Use open standards to build better and more
    maintainable APIs
    1

    View full-size slide

  2. $> WHOAMI
    $> BOYAN
    Developer at Shtrak
    Community cheerleader
    Wannabe speaker and writer
    Partialy-guilty for phpVarna
    2 . 1

    View full-size slide

  3. WHERE TO FIND ME
    @specter_bg
    boyanyordanov
    boyanyordanov
    2 . 2

    View full-size slide

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

    View full-size slide

  5. WHAT IS REST ?
    pretty URLs ?
    HTTP verbs for actions ?
    None of those ?
    3 . 2

    View full-size slide

  6. ACHIEVING ETERNAL GLORY
    Richardson Maturity Model post by Martin Fowler
    3 . 3

    View full-size slide

  7. SO ARE WE ... ?
    ... probably NOT. But does it really matter?
    3 . 4

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  11. BENEFITS OF USING STANDARDS FOR
    APIS
    easier to design and build
    easier to maintain
    easier to consume
    easier to test
    6 . 1

    View full-size slide

  12. 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

    View full-size slide

  13. {
    "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

    View full-size slide

  14. 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

    View full-size slide

  15. DECONSTRUCTING THE COMPOUND
    DOCUMENT
    TOP LEVEL ATTRIBUTES
    data
    errors
    meta
    links
    jsonapi
    included
    11 . 2

    View full-size slide

  16. TYPES OF OBJECTS
    resource object
    link object
    resource identi er object
    error object
    12 . 1

    View full-size slide

  17. 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

    View full-size slide

  18. LINKS OBJECT
    "links": {
    "self": "/meetups/1"
    }
    ...
    "links": {
    "related": {
    "href": "/meetups/1/attendees",
    "meta": {
    "count": 50
    }
    }
    }
    12 . 3

    View full-size slide

  19. RESOURCE IDENTIFIER OBJECT
    {
    "id": "1",
    "type": "meetups",
    "meta": {
    "attendee-count": 50
    }
    }
    12 . 4

    View full-size slide

  20. RELATIONSHIPS OBJECT
    "relationships": {
    "location": {
    "links": {
    "self": "meetups/1/relationships/venue",
    "related": "meetups/1/venue"
    },
    "data": { "type": "venues", "id": "1" }
    }
    }
    12 . 5

    View full-size slide

  21. 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

    View full-size slide

  22. 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

    View full-size slide

  23. 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

    View full-size slide

  24. 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

    View full-size slide

  25. 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

    View full-size slide

  26. 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

    View full-size slide

  27. 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

    View full-size slide

  28. PAGINATION
    GET /meetups?page[number]=3&page[size]=1 HTTP/1.1
    Accept: application/vnd.api+json
    16 . 3

    View full-size slide

  29. {
    "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

    View full-size slide

  30. RESOURCE OPERATIONS
    listing
    show 404 examples
    get individual resource
    create
    show validation errors examples
    update
    delete
    17

    View full-size slide

  31. 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

    View full-size slide

  32. FRACTAL
    Like a view layer for your
    JSON/YAML/etc.
    19

    View full-size slide

  33. MAIN CONCEPTS
    Fractal Manager
    Resource
    Includes
    Serializer
    Transformer
    Pagination
    via paginator
    via cursor
    20

    View full-size slide

  34. 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

    View full-size slide

  35. 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

    View full-size slide

  36. 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

    View full-size slide

  37. 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

    View full-size slide

  38. 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

    View full-size slide

  39. 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

    View full-size slide

  40. WHAT IS MISSING FROM THE SPEC?
    ltering
    sorting
    sparse eldsets
    pagination - present and usable but not spec
    compliant
    22 . 1

    View full-size slide

  41. 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

    View full-size slide

  42. SUPPORTED ADAPTERS
    Laravel
    Doctrine
    Zend Framework
    Phalcon
    Pagerfanta
    your own PaginatorInterface implementation
    22 . 3

    View full-size slide

  43. WHAT IS NOT INCLUDED ON
    PURPOSE?
    content negotiation
    response code handling
    error objects
    22 . 4

    View full-size slide

  44. HANDLING ERROR OBJECTS
    23

    View full-size slide

  45. Use a base class
    class JsonApiError extends \Exception {
    protected $status;
    protected $title;
    protected $detail;
    protected $source;
    protected $meta;
    public function toArray() {...}
    }
    24 . 1

    View full-size slide

  46. Extend it for speci c errors
    class NotFoundError extends JsonApiError {
    protected $status = 404;
    protected $title = 'Resource not found';
    }
    24 . 2

    View full-size slide

  47. EASIER FRACTAL
    composer install spatie/fractalistic
    A developer friendly wrapper around
    Fractal
    25 . 1

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  50. 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

    View full-size slide

  51. QUESTIONS ?
    29

    View full-size slide