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

Pragmatic APIs 101 (Symfony Camp UA)

Pragmatic APIs 101 (Symfony Camp UA)

Over the last five years, I have studied REST and HTTP APIs a lot. Nowadays, everyone claims to have a REST API, yet the REST architecture is complex and not really practicable in real life. In this talk, we will see that it does not matter to be 100% REST-compliant, and above all, how to build an API in a pragmatic manner.

---

Online slides: https://monod.lelab.tailordev.fr/f90f0b1a-ee04-4dbd-a12e-ec75f9c3a145#Q8Jg0lBfiskYIji4wY9fqIu3Z6CU0g+HeapaVNhAR+M=
Sources: https://github.com/willdurand-slides/pragmatic-apis-101

William Durand

October 29, 2016
Tweet

More Decks by William Durand

Other Decks in Programming

Transcript

  1. PRAGMATIC APIS 101
    William Durand — October 29th, 2016 — Symfony Camp UA
    1

    View full-size slide

  2. WHAT'S REST?
    2 . 1

    View full-size slide

  3. REPRESENTATIONAL STATE TRANSFER
    REST is the underlying architectural principle of the web.
    It is formalized as a set of constraints, described in Roy
    Fielding's PhD dissertation.
    2 . 2

    View full-size slide

  4. RICHARDSON MATURITY MODEL
    2 . 3

    View full-size slide

  5. LEVELS 0, 1, AND 2
    It is all about resources
    Client uses specific HTTP verbs
    Server uses HTTP status codes
    Content Negotiation
    2 . 4

    View full-size slide

  6. LEVEL 3 - HYPERMEDIA CONTROLS
    Service discovery via relations
    Hypermedia formats (e.g. HTML, HAL, JSONAPI)
    Resources are not important, their representations are!
    URI design does not matter (URI template)
    Client's experience is crucial!
    Protocol & application semantics
    2 . 5

    View full-size slide

  7. HATEOAS
    Hypermedia As The Engine Of Application State
    It means that hypermedia should be used to find your
    way through the API. It is all about state transitions.
    Your application is just a big state machine.
    2 . 6

    View full-size slide

  8. RESOURCE VS REPRESENTATION
    A resource can be anything, and can have more than one
    representation. A representation describes resource state.
    2 . 7

    View full-size slide

  9. PROTOCOL SEMANTICS
    How the underlying resource should behave under HTTP?
    2 . 8

    View full-size slide

  10. APPLICATION SEMANTICS
    What, specifically, will happen to the
    application or resource state?
    2 . 9

    View full-size slide

  11. THE SEMANTIC CHALLENGE*
    Some standards have a good protocol-level semantics but
    no application-level semantics (Collection+JSON, Atom).
    Some standards define a lot of application-level semantics
    but no protocol semantics (Microformats, Microdata).
    * = as described in Richardson and Amundsen's “RESTful Web APIs” book
    2 . 10

    View full-size slide

  12. PROFILES
    A profile is defined to not alter the semantics of the
    resource representation itself, but to allow clients to
    learn about additional semantics, .
    Does not have to be machine-readable but recommended.
    RFC 6906
    2 . 11

    View full-size slide

  13. EXAMPLES
    Traditional API documentation
    XMDP (for XHTML documents)
    ALPS (XMDP on steroids)
    Embedded doc. (as in HAL/Siren)
    JSON-LD, JSON Schema
    2 . 12

    View full-size slide

  14. STATE OF THE ART INDUSTRY
    3 . 1

    View full-size slide

  15. WE ALL WANT RESTFUL APIS
    3 . 2

    View full-size slide

  16. IS MY API RESTFUL WHEN I USE JSON?
    , The REST CookBook.
    Short answer: no. Long answer: no, not yet.
    RESTful API must use hypermedia formats. JSON is
    not a hypermedia format
    3 . 4

    View full-size slide

  17. NO RELATIONS = NO REST
    , Roy Fielding.
    If the engine of application state (and hence the API)
    is not being driven by hypertext, then it cannot be
    RESTful and cannot be a REST API
    3 . 5

    View full-size slide

  18. S/REST/HTTP++/
    REST APIs are a myth, i.e. too complex in real life.
    , Steve Klabnik.
    99.99% of the RESTful APIs out there aren’t fully compliant
    with Roy Fielding’s conception of REST
    3 . 6

    View full-size slide

  19. ARE WE ALL SCREWED AND SHOULD
    WE ALL JUMP TO GRAPHQL?
    3 . 7

    View full-size slide

  20. THE PRAGMATIC WAY
    Well-designed, pragmatic, and future-proof APIs.
    4

    View full-size slide

  21. DOCUMENTATION
    5 . 1

    View full-size slide

  22. API BLUEPRINT
    5 . 2

    View full-size slide

  23. MOOOOCKS!
    (Apiary / )
    Drakov
    5 . 6

    View full-size slide

  24. JSON SCHEMA
    (Apiary / Aglio thanks to )
    MSON
    + Attributes
    + data (object)
    + id: `123` (string, required) - The identifier
    {
    "$schema": "http://json-schema.org/draft-04/schema#",
    "type": "object",
    "properties": {
    "data": {
    "type": "object",
    "properties": {
    "id": { "type": "string", "description": "The identifier" }
    },
    "required": [ "id" ]
    }
    }
    }
    5 . 7

    View full-size slide

  25. (ex-Swagger)
    5 . 8

    View full-size slide

  26. SYMFONY/PHP
    NelmioApiDocBundle ( )
    by Liip (blog post)
    (JSON Schema) and (Open API)
    #900
    A Tool to Convert NelmioApiDocBundle to Swagger
    PHP
    Jane Jane Open Api
    5 . 9

    View full-size slide

  27. ADVICES
    Write documentation first, then code
    Must be under version control
    Test your documentation (build / )
    Dredd
    5 . 10

    View full-size slide

  28. SYMFONY/PHP
    = Great for HTTP++ APIs
    + = HAL
    , , etc.
    Symfony REST Edition
    FOSRestBundle Hateoas
    Fractal Negotiation
    6 . 2

    View full-size slide

  29. JSON API
    application/vnd.api+json
    {
    "data": {
    "id": "41c6cf",
    "type": "sequence",
    "attributes": {
    "name": "> Demo 1",
    "sequence": "ATCGAATCGGTTTAAAATCGATTCCCGAAAA"
    }
    },
    "links": {
    "parent": "/data/27d99b56",
    "self": "/data/27d99b56/41c6cf",
    "source": "https://example.org/demo-sequence.fasta",
    "profile": "/profiles/data/sequence"
    }
    }
    6 . 5

    View full-size slide

  30. ERRORS
    {
    "errors": [
    {
    "code": "404",
    "status": "404",
    "title": "Not Found",
    "detail": "The URL you are trying to reach does not exist.",
    "links": {
    "about": "https://httpstatuses.com/404",
    "profile": "/profiles/errors"
    }
    }
    ]
    }
    6 . 6

    View full-size slide

  31. UPDATE WITH PUT PATCH
    (Yes... I wrote about )
    PATCH /upload/jobs/27d99b56-9327-4d28-a69c-31229bf971aa
    Content-Type: application/vnd.api+json
    Accept: application/vnd.api+json
    // /!\ request payload below
    {
    "data": {
    "id": "27d99b56-9327-4d28-a69c-31229bf971aa",
    "type": "upload-jobs",
    "attributes": {
    "data_type": "sequence",
    "file_type": "fasta"
    }
    }
    }
    PATCH'ing correctly
    6 . 7

    View full-size slide

  32. SPARSE FIELDSETS
    with the fields query parameter
    GET /data/7d99b56?fields[sequence]=name
    Accept: application/vnd.api+json
    "data": [
    {
    "id": "41c6cf",
    "type": "sequence",
    "attributes": { "name": "> Demo 1" }
    },
    {
    "id": "787ff2",
    "type": "sequence",
    "attributes": { "name": "> Seq. 2" }
    }
    ]
    6 . 8

    View full-size slide

  33. FILTERING
    with the filter query parameter
    GET /data/27d99b56?fields[sequence]=sequence \
    &filter={"name": { "$regex": "/demo/", "$options": "i"}}
    Accept: application/vnd.api+json
    "data": [
    {
    "id": "41c6cf",
    "type": "sequence",
    "attributes": {
    "sequence": "ATCGAATCGGTTTAAAATCGATTCCCGAAAA"
    }
    }
    ]
    6 . 9

    View full-size slide

  34. PAGINATION
    with the page query parameter
    {
    "data": [ ... ],
    "links": {
    "next": "/data/8f7d5ef1?page[cursor]=57de7ff7&page[limit]=1",
    "prev": "/data/8f7d5ef1?page[cursor]=-57de7ff7&page[limit]=1"
    },
    "meta": {
    "page": {
    "after": "57de7ff7",
    "before": "-57de7ff7",
    "count": 3138,
    "limit": 1
    }
    }
    }
    6 . 10

    View full-size slide

  35. INCLUSION
    with the include query parameter
    GET /data/27d99b56?include=ANYTHING
    Accept: application/vnd.api+json
    {
    "data": [ ... ],
    "included": [
    ANYTHING
    ]
    }
    6 . 11

    View full-size slide

  36. ASYNCHRONOUS PROCESSING (1/3)
    POST /upload
    Content-Length: 87
    Content-Type: text/csv
    Accept: application/vnd.api+json
    HTTP/1.1 202 Accepted
    Content-Type: application/vnd.api+json
    Content-Location: /upload/jobs/27d99b56
    {
    "data": {
    "id": "27d99b56",
    "type": "upload-jobs",
    "attributes": {
    "status": "Processing..."
    }
    }
    }
    6 . 12

    View full-size slide

  37. ASYNCHRONOUS PROCESSING (2/3)
    GET /upload/jobs/27d99b56
    Accept: application/vnd.api+json
    HTTP/1.1 303 See other
    Location: /data/27d99b56
    6 . 13

    View full-size slide

  38. ASYNCHRONOUS PROCESSING (3/3)
    Degraded mode for browsers with the Prefer header
    GET /upload/jobs/27d99b56
    Accept: application/vnd.api+json
    Prefer: status=201
    HTTP/1.1 201 Created
    Location: /data/27d99b56
    6 . 14

    View full-size slide

  39. RECAP'
    JSON API helps writing REST APIs
    It is OK to take shortcuts (sometimes)
    JSON schema for (application) semantics
    6 . 15

    View full-size slide

  40. TESTING
    7 . 1

    View full-size slide

  41. PHPUNIT + GUZZLE
    Source:
    public function testPOST()
    {
    // ... create Guzzle $client
    $request = $client->post('/api/programmers', null, json_encode($data));
    $response = $request->send();
    $request = $client->post('/api/programmers', null, json_encode($data));
    $response = $request->send();
    $this->assertEquals(201, $response->getStatusCode());
    $this->assertTrue($response->hasHeader('Location'));
    $data = json_decode($response->getBody(true), true);
    $this->assertArrayHasKey('nickname', $data);
    }
    https://knpuniversity.com/screencast/rest/testing-phpunit
    7 . 2

    View full-size slide

  42. BEHAT
    Source:
    # api/features/programmer.feature
    # ...
    Scenario: Create a programmer
    Given I have the payload:
    """
    {
    "nickname": "ObjectOrienter",
    "avatarNumber" : "2",
    "tagLine": "I'm from a test!"
    }
    """
    When I request "POST /api/programmers"
    Then the response status code should be 201
    And the "nickname" property should equal "ObjectOrienter"
    https://knpuniversity.com/screencast/rest/testing
    7 . 3

    View full-size slide

  43. CHAKRAM
    describe("HTTP assertions", function () {
    it("should make HTTP assertions easy", function () {
    var response = chakram.get("http://httpbin.org/get?test=chakram");
    expect(response).to.have.status(200);
    expect(response).to.have.header("content-type", "application/json");
    expect(response).not.to.be.encoded.with.gzip;
    expect(response).to.comprise.of.json({
    args: { test: "chakram" }
    });
    return chakram.wait();
    });
    });
    http://dareid.github.io/chakram/
    7 . 4

    View full-size slide

  44. FRISBY
    var frisby = require('frisby');
    frisby.create('Get Brightbit Twitter feed')
    .get('https://api.twitter.com/1/statuses/user_timeline.json?screen_name=brightbit')
    .expectStatus(200)
    .expectHeaderContains('content-type', 'application/json')
    .expectJSON('0', {
    user: {
    verified: false,
    location: "Oklahoma City, OK",
    url: "http://brightb.it"
    }
    })
    .toss();
    http://frisbyjs.com
    7 . 5

    View full-size slide

  45. DREDD (FOR API DOC)
    https://github.com/apiaryio/dredd
    7 . 6

    View full-size slide

  46. POSTMAN
    7 . 7

    View full-size slide

  47. HURL . IT
    7 . 8

    View full-size slide

  48. AND...
    : cURL-like tool for humans
    : like sed for JSON data
    You can add this to your .zshrc:
    HTTPie
    jq
    jsonapi() {
    http "$@" Accept:application/vnd.api+json \
    Content-Type:application/vnd.api+json
    }
    7 . 9

    View full-size slide

  49. CONCLUSION
    8 . 1

    View full-size slide

  50. DOCUMENTATION
    1. Very important
    2. Write it first
    3. Test it
    8 . 2

    View full-size slide

  51. CODE
    1. I JSON API
    2. It is OK not to be 100% REST compliant
    3. TEST YOUR API!
    8 . 3

    View full-size slide

  52. TESTING
    1. No excuse!
    2. No excuse!
    3. No excuse!
    8 . 4

    View full-size slide

  53. ONE MORE THING
    I did not talk about security, but:
    API key / Authorization header are a good start
    OAuth is not for authentication, is!
    JWT ( ) is trendy
    Check out
    OpenID Connect
    LexikJWTAuthenticationBundle
    Auth0 article on Symfony
    9

    View full-size slide

  54. A: I DON'T KNOW.
    10

    View full-size slide

  55. THANK YOU.
    QUESTIONS?



    williamdurand.fr
    github.com/willdurand
    @couac
    11

    View full-size slide