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

API Tips from the Frontline

API Tips from the Frontline

Anna Filina
PRO

June 09, 2015
Tweet

More Decks by Anna Filina

Other Decks in Programming

Transcript

  1. foolab.ca | @foolabca
    API Tips From the
    Frontline
    IPC, Berlin - June 9, 2015

    View Slide

  2. Anna Filina
    • Developer
    • Problem solver
    • Teacher
    • Advisor
    • FooLab + ConFoo
    2

    View Slide

  3. What you will learn
    • Don't repeat my mistakes
    • Overcome common obstacles
    • Build elegant & pragmatic APIs
    4

    View Slide

  4. Endpoints
    What URL to call when

    View Slide

  5. Lists
    • /products
    • /products?category=games,movies&lang=fr
    • /products?include=platforms
    • /products?sort=-date
    • /products?page[number]=2&page[size]=100
    • /products?lang=fr
    6

    View Slide

  6. Request/Response
    How to format stuff

    View Slide

  7. Request
    • Bad: Content-Type: application/x-www-form-urlencoded
    • Content-Type: application/json
    • Send content (POST) in body, not headers
    8

    View Slide

  8. Request example: add
    POST /products HTTP/1.1
    Host: api.example.org
    Content-Type: application/json; charset=UTF-8
    {
    "data": {
    "name": "Skyrim",
    "price": 19.99
    }
    }
    9

    View Slide

  9. Response example: list
    {
    "data": [
    {"name": "Skyrim"},
    {"name": "Civilization V"}
    ],
    "page": {
    "number": 1,
    "size": 2,
    "pages": 50,
    "total": 100
    }
    }
    10

    View Slide

  10. 3 queries
    • Count query with same filters but no pagination
    • Query with filters, pagination & sorting, but only select
    DISTINCT IDs
    ◦ Because JOIN + LIMIT = wrong number of root records
    • Query all the data (with joins) by IDs and apply sorting
    11

    View Slide

  11. LIMIT
    SELECT * FROM game
    LEFT JOIN rating ON rating.game_id = game.id
    WHERE game.price < 20
    LIMIT 2;
    12
    id name price game_id score
    1 Skyrim 19.99 1 9.1
    1 Skyrim 19.99 1 8.9
    2 Diablo 3 17.89 2 8.5

    View Slide

  12. Upload
    • Use tools, don't DIY (do it yourself).
    • Client-side: Symfony
    • Client-side (more dynamic): Plupload
    • Step 1: upload temp file.
    • Step 2: give file path to next API request.
    • Server-side: Guzzle.
    13

    View Slide

  13. Plupload
    var uploader = new plupload.Uploader({
    runtimes: 'html5,html4',
    max_file_size: '5mb',
    url: '/api/upload',
    filters: [{extensions: 'jpg,png,jpeg'}]
    });
    uploader.init();
    14

    View Slide

  14. Guzzle

    $url = 'http://example.org/profiles/1/edit';
    $request = $client->createRequest('PATCH', $url);
    $reqBody = $request->getBody();
    $reqBody->setField('data', ['first_name':'Anna']);
    $file = new PostFile('i.jpg', fopen('/path', 'r'));
    $reqBody->addFile($file);
    $response = $client->send($request);
    15

    View Slide

  15. Status codes
    • Don't confuse API & HTTP codes
    • 2xx success
    • 3xx redirect
    • 4xx client error
    • 5xx server error
    • Send API-specific code in body
    16

    View Slide

  16. Example of error
    Status: 400
    Content-Type: application/json; charset=UTF-8
    {
    "error": {
    "code": 1001,
    "message": "Price must be greater than 0."
    }
    }
    17

    View Slide

  17. Testing
    Simpler than you think

    View Slide

  18. HTTP
    • Use Guzzle or built-in framework tool
    • Generate HTTP request , compare output
    19

    View Slide

  19. Guzzle test
    // tests/ApiProductTest.php
    public function testGetOneProduct()
    {
    $client = new Client();
    $response = $client->get('http://example.org/products/1', [
    'exceptions' => false,
    'headers' => ['Accept' => 'application/json']
    ]);
    $this->assertEquals(200, $response->getStatusCode());
    // ...
    }
    20

    View Slide

  20. Guzzle test
    // ...
    $body = $response->getBody()->getContents();
    $this->assertJsonStringEqualsJsonString('{
    "data": {
    "id": "1",
    "name": "Skyrim",
    "price": 19.99
    }
    }', $body);
    21

    View Slide

  21. Testing tips
    • Create separate database for tests
    • Write tests before you code:
    • TDD
    • Contract between you and client dev
    22

    View Slide

  22. Authentication
    Nooo, I hate that part! Someone else code it plz.

    View Slide

  23. Multiple methods
    • Don't send username/password in each request
    ◦ Especially with untrusted 3rd parties
    ◦ Especially if no SSL
    • You can have multiple auth methods for one API
    • Sessions similar to tokens
    24

    View Slide

  24. OAuth2
    • SSL required (can be risky)
    • Advanced features like access scope
    • Can be overkill if you need basic features
    • Private credentials
    25

    View Slide

  25. OAuth2 - conceptual diagram
    26
    User Client app

    (php/js/mobile)
    API
    Request Forward
    Validate
    Create
    token
    Store
    token
    Login form
    Login
    token
    user/pass

    View Slide

  26. Digest
    • Its own encryption
    • Easy to implement
    • No replay (nonce)
    • Comes out-of-the-box with some frameworks
    27

    View Slide

  27. Digest - conceptual diagram
    28
    Request
    User/client app API
    Unauthorized
    Validate
    digest (nonce,pass)
    Request
    nonce

    View Slide

  28. Refactoring to API
    Don't rewrite all the legacy at once

    View Slide

  29. Progressive rewrite
    • Rewrite one component at a time
    • Start with whatever has fewer dependencies (or critical)
    • Delete dead code
    • Copy production data for dev environment
    30

    View Slide

  30. Services vs libraries
    • Services: GET /products?id=1,5
    • Libraries:
    • Bypass HTTP
    • $db->getList( ["id" => "1,5"] );
    • Can fetch without pagination
    • Can hydrate to models
    31

    View Slide

  31. Implementation tips
    Is this a good way to code it?

    View Slide

  32. Libraries
    • I use Symfony & Doctrine
    • Aside from frameworks, I prefer small tools that don't do
    too much magic
    33

    View Slide

  33. Reuse & standardize
    • Goal: streamline endpoint creation
    • Base controller for common request processing
    • Base repository for querying with filters
    • Keep things fully customizable
    34

    View Slide

  34. Performance
    Make things faster. Much faster.

    View Slide

  35. Benchmark
    • Give Tideways or Blackfire a spin
    • Make performance part of your test suite
    • In dev/test mode, use a meta block
    36

    View Slide

  36. Example
    {
    "meta": {
    "perf": {
    "db_time": 0.00367,
    "total_time": 0.3120,
    "memory": 19661
    }
    }
    }
    37

    View Slide

  37. Performance tips
    • Don't use lazy loading. Example: $product->getPhotos().
    • Craft your own joins and carefully select fields.
    • Avoid ORM built-in hydration for read operations.
    • Use API keys even for public endpoints (DDoS
    mitigation).
    • Put stuff in Memcached/Redis (especially blocked keys).
    • HTTP server can check headers
    38

    View Slide

  38. Useful links
    • Standard API format

    http://jsonapi.org/format/
    • Digest implementation

    http://php.net/manual/en/features.http-auth.php
    • Symfony components

    http://symfony.com/doc/current/components/index.html
    • Book "Build APIs You Won't Hate"

    https://leanpub.com/build-apis-you-wont-hate
    39

    View Slide

  39. Anna Filina
    • Development: PHP, JS, etc.
    • Fix problems: bugs, performance, etc.
    • Workshops: testing, Symfony, AngularJS, API, etc.
    • Advisor: testing strategy, legacy code, etc.
    40

    View Slide

  40. @afilina afilina.com

    View Slide