Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

Endpoints What URL to call when

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

Request/Response How to format stuff

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

Guzzle 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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

Testing Simpler than you think

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

OAuth2 - conceptual diagram 26 User Client app
 (php/js/mobile) API Request Forward Validate Create token Store token Login form Login token user/pass

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

Implementation tips Is this a good way to code it?

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

Performance Make things faster. Much faster.

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

@afilina afilina.com