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 Slide

  2. WHAT'S REST?
    2 . 1

    View 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 Slide

  4. RICHARDSON MATURITY MODEL
    2 . 3

    View 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 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 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 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 Slide

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

    View Slide

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

    View 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 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 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 Slide

  14. STATE OF THE ART INDUSTRY
    3 . 1

    View Slide

  15. WE ALL WANT RESTFUL APIS
    3 . 2

    View Slide

  16. YES.
    3 . 3

    View Slide

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

  18. 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 Slide

  19. 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 Slide

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

    View Slide

  21. NOPE.
    3 . 8

    View Slide

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

    View Slide

  23. DOCUMENTATION
    5 . 1

    View Slide

  24. API BLUEPRINT
    5 . 2

    View Slide

  25. APIARY
    5 . 3

    View Slide

  26. APIARY
    5 . 4

    View Slide

  27. AGLIO
    5 . 5

    View Slide

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

    View Slide

  29. 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 Slide

  30. (ex-Swagger)
    5 . 8

    View Slide

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

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

    View Slide

  33. CODE
    6 . 1

    View Slide

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

    View Slide

  35. 6 . 3

    View Slide

  36. 6 . 4

    View Slide

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

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

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

  40. 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 Slide

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

  42. 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 Slide

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

    View Slide

  44. 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 Slide

  45. 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 Slide

  46. 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 Slide

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

    View Slide

  48. TESTING
    7 . 1

    View Slide

  49. 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 Slide

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

  51. 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 Slide

  52. 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 Slide

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

    View Slide

  54. POSTMAN
    7 . 7

    View Slide

  55. HURL . IT
    7 . 8

    View Slide

  56. 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 Slide

  57. CONCLUSION
    8 . 1

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  61. 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 Slide

  62. A: I DON'T KNOW.
    10

    View Slide

  63. THANK YOU.
    QUESTIONS?



    williamdurand.fr
    github.com/willdurand
    @couac
    11

    View Slide