API Tips from the Frontline

API Tips from the Frontline

B3b2139e4f2c0eca4efe2379fcebc1c5?s=128

Anna Filina

June 09, 2015
Tweet

Transcript

  1. foolab.ca | @foolabca API Tips From the Frontline IPC, Berlin

    - June 9, 2015
  2. Anna Filina • Developer • Problem solver • Teacher •

    Advisor • FooLab + ConFoo 2
  3. What you will learn • Don't repeat my mistakes •

    Overcome common obstacles • Build elegant & pragmatic APIs 4
  4. Endpoints What URL to call when

  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
  6. Request/Response How to format stuff

  7. Request • Bad: Content-Type: application/x-www-form-urlencoded • Content-Type: application/json • Send

    content (POST) in body, not headers 8
  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
  9. Response example: list { "data": [ {"name": "Skyrim"}, {"name": "Civilization

    V"} ], "page": { "number": 1, "size": 2, "pages": 50, "total": 100 } } 10
  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
  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
  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
  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
  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
  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
  16. Example of error Status: 400 Content-Type: application/json; charset=UTF-8 { "error":

    { "code": 1001, "message": "Price must be greater than 0." } } 17
  17. Testing Simpler than you think

  18. HTTP • Use Guzzle or built-in framework tool • Generate

    HTTP request , compare output 19
  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
  20. Guzzle test // ... $body = $response->getBody()->getContents(); $this->assertJsonStringEqualsJsonString('{ "data": {

    "id": "1", "name": "Skyrim", "price": 19.99 } }', $body); 21
  21. Testing tips • Create separate database for tests • Write

    tests before you code: • TDD • Contract between you and client dev 22
  22. Authentication Nooo, I hate that part! Someone else code it

    plz.
  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
  24. OAuth2 • SSL required (can be risky) • Advanced features

    like access scope • Can be overkill if you need basic features • Private credentials 25
  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
  26. Digest • Its own encryption • Easy to implement •

    No replay (nonce) • Comes out-of-the-box with some frameworks 27
  27. Digest - conceptual diagram 28 Request User/client app API Unauthorized

    Validate digest (nonce,pass) Request nonce
  28. Refactoring to API Don't rewrite all the legacy at once

  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
  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
  31. Implementation tips Is this a good way to code it?

  32. Libraries • I use Symfony & Doctrine • Aside from

    frameworks, I prefer small tools that don't do too much magic 33
  33. Reuse & standardize • Goal: streamline endpoint creation • Base

    controller for common request processing • Base repository for querying with filters • Keep things fully customizable 34
  34. Performance Make things faster. Much faster.

  35. Benchmark • Give Tideways or Blackfire a spin • Make

    performance part of your test suite • In dev/test mode, use a meta block 36
  36. Example { "meta": { "perf": { "db_time": 0.00367, "total_time": 0.3120,

    "memory": 19661 } } } 37
  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
  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
  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
  40. @afilina afilina.com