API Tips from the Frontline

API Tips from the Frontline

B3b2139e4f2c0eca4efe2379fcebc1c5?s=128

Anna Filina

June 09, 2015
Tweet

Transcript

  1. 3.

    What you will learn • Don't repeat my mistakes •

    Overcome common obstacles • Build elegant & pragmatic APIs 4
  2. 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
  3. 9.

    Response example: list { "data": [ {"name": "Skyrim"}, {"name": "Civilization

    V"} ], "page": { "number": 1, "size": 2, "pages": 50, "total": 100 } } 10
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. 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
  10. 16.

    Example of error Status: 400 Content-Type: application/json; charset=UTF-8 { "error":

    { "code": 1001, "message": "Price must be greater than 0." } } 17
  11. 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
  12. 21.

    Testing tips • Create separate database for tests • Write

    tests before you code: • TDD • Contract between you and client dev 22
  13. 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
  14. 24.

    OAuth2 • SSL required (can be risky) • Advanced features

    like access scope • Can be overkill if you need basic features • Private credentials 25
  15. 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
  16. 26.

    Digest • Its own encryption • Easy to implement •

    No replay (nonce) • Comes out-of-the-box with some frameworks 27
  17. 27.
  18. 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
  19. 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
  20. 32.

    Libraries • I use Symfony & Doctrine • Aside from

    frameworks, I prefer small tools that don't do too much magic 33
  21. 33.

    Reuse & standardize • Goal: streamline endpoint creation • Base

    controller for common request processing • Base repository for querying with filters • Keep things fully customizable 34
  22. 35.

    Benchmark • Give Tideways or Blackfire a spin • Make

    performance part of your test suite • In dev/test mode, use a meta block 36
  23. 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
  24. 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
  25. 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