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. 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
  2. LEVELS 0, 1, AND 2 It is all about resources

    Client uses specific HTTP verbs Server uses HTTP status codes Content Negotiation 2 . 4
  3. 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
  4. 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
  5. RESOURCE VS REPRESENTATION A resource can be anything, and can

    have more than one representation. A representation describes resource state. 2 . 7
  6. 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
  7. 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
  8. EXAMPLES Traditional API documentation XMDP (for XHTML documents) ALPS (XMDP

    on steroids) Embedded doc. (as in HAL/Siren) JSON-LD, JSON Schema 2 . 12
  9. 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
  10. 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
  11. 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
  12. 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
  13. 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
  14. ADVICES Write documentation first, then code Must be under version

    control Test your documentation (build / ) Dredd 5 . 10
  15. SYMFONY/PHP = Great for HTTP++ APIs + = HAL ,

    , etc. Symfony REST Edition FOSRestBundle Hateoas Fractal Negotiation 6 . 2
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. RECAP' JSON API helps writing REST APIs It is OK

    to take shortcuts (sometimes) JSON schema for (application) semantics 6 . 15
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30.  CODE 1. I  JSON API 2. It is

    OK not to be 100% REST compliant 3. TEST YOUR API! 8 . 3
  31. 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