A presentation at phpday 2022 in in Verona, VR, Italy by Rob Allen
The Right API for the Job Rob Allen PHPDay, May 2022
Fit for Purpose Rob Allen ~ @akrabat
API Architecture Rob Allen ~ @akrabat
APIs can be realised in any style but, which makes the most sense? Rob Allen ~ @akrabat
RPC APIs Rob Allen ~ @akrabat
RPC APIs • Call a function on a remote server Rob Allen ~ @akrabat
RPC APIs • Call a function on a remote server • Common implementations: JSON-RPC, SOAP, gRPC Rob Allen ~ @akrabat
RPC APIs • Call a function on a remote server • Common implementations: JSON-RPC, SOAP, gRPC • Tends to require a schema (WSDL, ProtoBuf Defintion) Rob Allen ~ @akrabat
Ethereum JSON-RPC Request: POST / HTTP/1.1 Host: localhost:8545 { “jsonrpc”:”2.0”, “id”:1, “method”:”net_peerCount”, “params”:[] } Rob Allen ~ @akrabat
Ethereum JSON-RPC Response: { “id”:1, “jsonrpc”: “2.0”, “result”: “0x2” } Rob Allen ~ @akrabat
gRPC Interact via PHP library: $client = new RouteGuideClient(‘localhost:50051’); $p = new Routeguide\Point(); $p->setLatitude(409146138); $p->setLongitude(-746188906); list($feature, $status) = $client->GetFeature($p)->wait(); Rob Allen ~ @akrabat
RESTful APIs Rob Allen ~ @akrabat
RESTful APIs • Operate on a representation of the state of a resource though HTTP verbs Rob Allen ~ @akrabat
RESTful APIs • Operate on a representation of the state of a resource though HTTP verbs • HTTP native Rob Allen ~ @akrabat
RESTful APIs • Operate on a representation of the state of a resource though HTTP verbs • HTTP native • Uniform interface Rob Allen ~ @akrabat
RESTful APIs • Operate on a representation of the state of a resource though HTTP verbs • HTTP native • Uniform interface • Hypermedia controls Rob Allen ~ @akrabat
RESTful APIs PUT /users/ba60c99fd3 Content-Type: application/json { “name”: “Rob Allen” “email”: “rob@akrabat.com” } Rob Allen ~ @akrabat
RESTful APIs PUT /users/ba60c99fd3 Content-Type: application/json { “name”: “Rob Allen” “email”: “rob@akrabat.com” } Rob Allen ~ @akrabat
RESTful APIs PUT /users/ba60c99fd3 Content-Type: application/json { “name”: “Rob Allen” “email”: “rob@akrabat.com” } Rob Allen ~ @akrabat
RESTful APIs HTTP/1.1 201 Created ETag: dfb9f2ab35fe4d17bde2fb2b1cee88c1 Content-Type: application/json { “name”: “Rob Allen” “email”: “rob@akrabat.com”, “_links”: { “self”: “https://api.example.com/user/ba60c99fd3” } } Rob Allen ~ @akrabat
RESTful APIs HTTP/1.1 201 Created ETag: dfb9f2ab35fe4d17bde2fb2b1cee88c1 Content-Type: application/json { “name”: “Rob Allen” “email”: “rob@akrabat.com”, “_links”: { “self”: “https://api.example.com/user/ba60c99fd3” } } Rob Allen ~ @akrabat
RESTful APIs HTTP/1.1 201 Created ETag: dfb9f2ab35fe4d17bde2fb2b1cee88c1 Content-Type: application/json { “name”: “Rob Allen” “email”: “rob@akrabat.com”, “_links”: { “self”: “https://api.example.com/user/ba60c99fd3” } } Rob Allen ~ @akrabat
RESTful APIs HTTP/1.1 201 Created ETag: dfb9f2ab35fe4d17bde2fb2b1cee88c1 Content-Type: application/json { “name”: “Rob Allen” “email”: “rob@akrabat.com”, “_links”: { “self”: “https://api.example.com/user/ba60c99fd3” } } Rob Allen ~ @akrabat
GraphQL APIs Rob Allen ~ @akrabat
GraphQL APIs • Retrieve only the data you need on consumer side Rob Allen ~ @akrabat
GraphQL APIs • Retrieve only the data you need on consumer side • Reduce the number of calls to retrieve data with embedded resources Rob Allen ~ @akrabat
GraphQL APIs • Retrieve only the data you need on consumer side • Reduce the number of calls to retrieve data with embedded resources • Self-describing schema Rob Allen ~ @akrabat
Queries query { author(name: “Ann McCaffrey”) { id, name books(first: 5) { totalCount edges { node { id, title, datePublished } } } } } Rob Allen ~ @akrabat
Queries query { author(name: “Ann McCaffrey”) { id, name books(first: 5) { totalCount edges { node { id, title, datePublished } } } } } Rob Allen ~ @akrabat
Queries query { author(name: “Ann McCaffrey”) { id, name books(first: 5) { totalCount edges { node { id, title, datePublished } } } } } Rob Allen ~ @akrabat
Queries query { author(name: “Ann McCaffrey”) { id, name books(first: 5) { totalCount edges { node { id, title, datePublished } } } } } Rob Allen ~ @akrabat
Queries query { author(name: “Ann McCaffrey”) { id, name books(first: 5) { totalCount edges { node { id, title, datePublished } } } } } Rob Allen ~ @akrabat
Queries query { author(name: “Ann McCaffrey”) { id, name books(first: 5) { totalCount edges { node { id, title, datePublished } } } } } Rob Allen ~ @akrabat
Queries query { author(name: “Ann McCaffrey”) { id, name books(first: 5) { totalCount edges { node { id, title, datePublished } } } } } Rob Allen ~ @akrabat
Queries “data”: { “author”: { “id”: “MXxBdXRob3J8ZjA”, “name”: “Ann McCaffrey”, “books”: { “totalCount”: 6, “edges”: [ { “node”: { “id”: “MXxCb29rfGYwNzU”, “title”: “Dragonflight” } }, Rob Allen ~ @akrabat
Queries “data”: { “author”: { “id”: “MXxBdXRob3J8ZjA”, “name”: “Ann McCaffrey”, “books”: { “totalCount”: 6, “edges”: [ { “node”: { “id”: “MXxCb29rfGYwNzU”, “title”: “Dragonflight” } }, Rob Allen ~ @akrabat
Queries “data”: { “author”: { “id”: “MXxBdXRob3J8ZjA”, “name”: “Ann McCaffrey”, “books”: { “totalCount”: 6, “edges”: [ { “node”: { “id”: “MXxCb29rfGYwNzU”, “title”: “Dragonflight” } }, Rob Allen ~ @akrabat
Queries “data”: { “author”: { “id”: “MXxBdXRob3J8ZjA”, “name”: “Ann McCaffrey”, “books”: { “totalCount”: 6, “edges”: [ { “node”: { “id”: “MXxCb29rfGYwNzU”, “title”: “Dragonflight” } }, Rob Allen ~ @akrabat
Mutations mutation { createAuthor( name:”Mary Shelley”, dateOfBirth: “1797-08-30” ) { returning { id, name } } } Rob Allen ~ @akrabat
Mutations mutation { createAuthor( name:”Mary Shelley”, dateOfBirth: “1797-08-30” ) { returning { id, name } } } Rob Allen ~ @akrabat
Mutations mutation { createAuthor( name:”Mary Shelley”, dateOfBirth: “1797-08-30” ) { returning { id, name } } } Rob Allen ~ @akrabat
Mutations mutation { createAuthor( name:”Mary Shelley”, dateOfBirth: “1797-08-30” ) { returning { id, name } } } Rob Allen ~ @akrabat
Mutations Response: “data”: { “createAuthor”: { “returning”: [ { “id”: “e3388cbea4e840a”, “name”: “Mary Shelly”, } ] } } Rob Allen ~ @akrabat
Which to pick? Rob Allen ~ @akrabat
Lamborghini or Ferrari? Rob Allen ~ @akrabat
Lamborghini or Truck? Rob Allen ~ @akrabat
Considerations • • • • What is it to be used for? Response customisation requirements HTTP interoperability requirements Binary protocol? Rob Allen ~ @akrabat
Response customisation • GraphQL is a query-first language • REST tends towards less customisation • With RPC you get what you’re given! (None will fix your database layer’s ability to efficiently retreive the data requested!) Rob Allen ~ @akrabat
Performance • REST and RPC puts server performance first • GraphQL puts client performance first Rob Allen ~ @akrabat
Caching • GraphQL and RPC can only cache at application layer • REST can additionally cache at HTTP layer Rob Allen ~ @akrabat
Data Transfer GraphQL: query { avatar(userId: “1234”) } { “data”: { “avatar”: “(base64 data)” “format”: “image/jpeg” } RPC: POST /api { “method”: “getAvatar”, “userId”: “1234” } { “result”: “(base64 data)” } }} Rob Allen ~ @akrabat
Data Transfer REST: REST: GET /user/1234/avatar Accept: image/jpeg GET /user/1234/avatar Accept: application/json HTTP/1.1 200 OK {jpg image data} HTTP/1.1 200 OK {“data”: “(base64 data)”} Rob Allen ~ @akrabat
Versioning • RPC, GraphQL and REST can all version via evolution as easily as each other Rob Allen ~ @akrabat
Versioning • RPC, GraphQL and REST can all version via evolution as easily as each other • GraphQL is very good for deprecation of specific fields Rob Allen ~ @akrabat
Design considerations It’s always hard! Rob Allen ~ @akrabat
Design considerations It’s always hard! Rob Allen ~ @akrabat
It’s your choice Rob Allen ~ @akrabat
Developer Experience Rob Allen ~ @akrabat
Correctness Rob Allen ~ @akrabat
Correctness RPC: Functions! Rob Allen ~ @akrabat
Correctness RPC: Functions! REST: HTTP matters! Rob Allen ~ @akrabat
Correctness RPC: Functions! REST: HTTP matters! GraphQL: Think in terms of relationships! Rob Allen ~ @akrabat
Correctness RPC: Functions! REST: HTTP matters! GraphQL: Think in terms of relationships! Rob Allen ~ @akrabat
Errors Rob Allen ~ @akrabat
Errors Error representations must be first class citizens Rob Allen ~ @akrabat
Errors Error representations must be first class citizens Rob Allen ~ @akrabat
Documentation Rob Allen ~ @akrabat
Documentation • API Reference Rob Allen ~ @akrabat
Documentation • API Reference • Tutorials Rob Allen ~ @akrabat
To sum up Rob Allen ~ @akrabat
If you suck at providing a REST API, you will suck at providing a GraphQL API Arnaud Lauret, API Handyman Rob Allen ~ @akrabat
Thank you! https://joind.in/talk/8cdd9 Rob Allen ~ @akrabat
Photo credits - Architecture: https://www.flickr.com/photos/shawnstilwell/4335732627 - Choose Pill: https://www.flickr.com/photos/eclib/4905907267 - Lamborghini & Ferrari: https://akrab.at/3w0yFmg - Lamborghini & Truck: https://akrab.at/3F4kAZk - ’50s Computer: https://www.flickr.com/photos/9479603@N02/49755349401 - Blackboard: https://www.flickr.com/photos/bryanalexander/17182506391 - Crash Test: https://www.flickr.com/photos/astrablog/4133302216 Rob Allen ~ @akrabat
When you’ve been tasked with creating an HTTP API, there’s many things that you need to consider and I’m here to help you navigate your journey. We start with the fundamental decision of architecture: should your API be RESTful? What about GraphQL? or RPC? We’ll look at the choices and their strengths and weaknesses to guide your decision. We’ll also take a hard look at the other, vital components of an API from how it works with HTTP, through validation, payload formats and error handling. An API makes its mark when it is used, so we’ll also consider developer experience to ensure that your API is best it can be. By the end of this talk, you’ll be well-placed to design and deliver a great API that’s fit for purpose.