A presentation at PHP UK in in London, UK by Rob Allen
GraphQL, REST or RPC? Making the choice! Rob Allen PHPUK, February 2023
Fit for Purpose Rob Allen @akrabat @rob@akrabat.com
API Architecture Rob Allen @akrabat @rob@akrabat.com
APIs can be realised in any style but, which makes the most sense? Rob Allen @akrabat @rob@akrabat.com
RPC APIs Rob Allen @akrabat @rob@akrabat.com
RPC APIs • Call a function on a remote server Rob Allen @akrabat @rob@akrabat.com
RPC APIs • Call a function on a remote server • Common implementations: JSON-RPC, SOAP, gRPC Rob Allen @akrabat @rob@akrabat.com
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 @rob@akrabat.com
Ethereum JSON-RPC Request: POST / HTTP/1.1 Host: localhost:8545 { “jsonrpc”:”2.0”, “id”:1, “method”:”net_peerCount”, “params”:[] } Rob Allen @akrabat @rob@akrabat.com
Ethereum JSON-RPC Response: { “id”:1, “jsonrpc”: “2.0”, “result”: “0x2” } Rob Allen @akrabat @rob@akrabat.com
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 @rob@akrabat.com
RESTful APIs Rob Allen @akrabat @rob@akrabat.com
RESTful APIs • Operate on a representation of the state of a resource though HTTP verbs Rob Allen @akrabat @rob@akrabat.com
RESTful APIs • Operate on a representation of the state of a resource though HTTP verbs • HTTP native Rob Allen @akrabat @rob@akrabat.com
RESTful APIs • Operate on a representation of the state of a resource though HTTP verbs • HTTP native • Uniform interface Rob Allen @akrabat @rob@akrabat.com
RESTful APIs • Operate on a representation of the state of a resource though HTTP verbs • HTTP native • Uniform interface • Hypermedia controls Rob Allen @akrabat @rob@akrabat.com
RESTful APIs PUT /users/ba60c99fd3 Content-Type: application/json Accept: application/json { “name”: “Rob Allen” “email”: “rob@akrabat.com” } Rob Allen @akrabat @rob@akrabat.com
RESTful APIs PUT /users/ba60c99fd3 Content-Type: application/json Accept: application/json { “name”: “Rob Allen” “email”: “rob@akrabat.com” } Rob Allen @akrabat @rob@akrabat.com
RESTful APIs PUT /users/ba60c99fd3 Content-Type: application/json Accept: application/json { “name”: “Rob Allen” “email”: “rob@akrabat.com” } Rob Allen @akrabat @rob@akrabat.com
RESTful APIs PUT /users/ba60c99fd3 Content-Type: application/json Accept: application/json { “name”: “Rob Allen” “email”: “rob@akrabat.com” } Rob Allen @akrabat @rob@akrabat.com
RESTful APIs: Response HTTP/1.1 201 Created Content-Type: application/hal+json ETag: dfb9f2ab35fe4d17bde2fb2b1cee88c1 { “name”: “Rob Allen” “email”: “rob@akrabat.com”, “_links”: { “self”: “https://api.example.com/user/ba60c99fd3” } } Rob Allen @akrabat @rob@akrabat.com
RESTful APIs HTTP/1.1 201 Created Content-Type: application/hal+json ETag: dfb9f2ab35fe4d17bde2fb2b1cee88c1 { “name”: “Rob Allen” “email”: “rob@akrabat.com”, “_links”: { “self”: “https://api.example.com/user/ba60c99fd3” } } Rob Allen @akrabat @rob@akrabat.com
RESTful APIs HTTP/1.1 201 Created Content-Type: application/hal+json ETag: dfb9f2ab35fe4d17bde2fb2b1cee88c1 { “name”: “Rob Allen” “email”: “rob@akrabat.com”, “_links”: { “self”: “https://api.example.com/user/ba60c99fd3” } } Rob Allen @akrabat @rob@akrabat.com
RESTful APIs HTTP/1.1 201 Created Content-Type: application/hal+json ETag: dfb9f2ab35fe4d17bde2fb2b1cee88c1 { “name”: “Rob Allen” “email”: “rob@akrabat.com”, “_links”: { “self”: “https://api.example.com/user/ba60c99fd3” } } Rob Allen @akrabat @rob@akrabat.com
GraphQL APIs Rob Allen @akrabat @rob@akrabat.com
GraphQL APIs • Retrieve only the data you need on consumer side Rob Allen @akrabat @rob@akrabat.com
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 @rob@akrabat.com
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 @rob@akrabat.com
Queries query { author(name: “Anne McCaffrey”) { id, name books(first: 5) { totalCount edges { node { id, title } } } } } Rob Allen @akrabat @rob@akrabat.com
Queries query { author(name: “Anne McCaffrey”) { id, name books(first: 5) { totalCount edges { node { id, title } } } } } Rob Allen @akrabat @rob@akrabat.com
Queries query { author(name: “Anne McCaffrey”) { id, name books(first: 5) { totalCount edges { node { id, title } } } } } Rob Allen @akrabat @rob@akrabat.com
Queries query { author(name: “Anne McCaffrey”) { id, name books(first: 5) { totalCount edges { node { id, title } } } } } Rob Allen @akrabat @rob@akrabat.com
Queries query { author(name: “Anne McCaffrey”) { id, name books(first: 5) { totalCount edges { node { id, title } } } } } Rob Allen @akrabat @rob@akrabat.com
Queries query { author(name: “Anne McCaffrey”) { id, name books(first: 5) { totalCount edges { node { id, title } } } } } Rob Allen @akrabat @rob@akrabat.com
Queries query { author(name: “Anne McCaffrey”) { id, name books(first: 5) { totalCount edges { node { id, title } } } } } Rob Allen @akrabat @rob@akrabat.com
Queries: Result “data”: { “author”: { “id”: “MXxBdXRob3J8ZjA”, “name”: “Anne McCaffrey”, “books”: { “totalCount”: 6, “edges”: [ { “node”: { “id”: “MXxCb29rfGYwNzU”, “title”: “Dragonflight” } }, Rob Allen @akrabat @rob@akrabat.com
Queries: Result “data”: { “author”: { “id”: “MXxBdXRob3J8ZjA”, “name”: “Anne McCaffrey”, “books”: { “totalCount”: 6, “edges”: [ { “node”: { “id”: “MXxCb29rfGYwNzU”, “title”: “Dragonflight” } }, Rob Allen @akrabat @rob@akrabat.com
Queries: Result “data”: { “author”: { “id”: “MXxBdXRob3J8ZjA”, “name”: “Anne McCaffrey”, “books”: { “totalCount”: 6, “edges”: [ { “node”: { “id”: “MXxCb29rfGYwNzU”, “title”: “Dragonflight” } }, Rob Allen @akrabat @rob@akrabat.com
Queries: Result “data”: { “author”: { “id”: “MXxBdXRob3J8ZjA”, “name”: “Anne McCaffrey”, “books”: { “totalCount”: 6, “edges”: [ { “node”: { “id”: “MXxCb29rfGYwNzU”, “title”: “Dragonflight” } }, Rob Allen @akrabat @rob@akrabat.com
Mutations mutation { createAuthor( name: “Mary Shelley”, dateOfBirth: “1797-08-30” ) { returning { id, name } } } Rob Allen @akrabat @rob@akrabat.com
Mutations mutation { createAuthor( name: “Mary Shelley”, dateOfBirth: “1797-08-30” ) { returning { id, name } } } Rob Allen @akrabat @rob@akrabat.com
Mutations mutation { createAuthor( name: “Mary Shelley”, dateOfBirth: “1797-08-30” ) { returning { id, name } } } Rob Allen @akrabat @rob@akrabat.com
Mutations mutation { createAuthor( name: “Mary Shelley”, dateOfBirth: “1797-08-30” ) { returning { id, name } } } Rob Allen @akrabat @rob@akrabat.com
Mutations: Response Response: “data”: { “createAuthor”: { “returning”: [ { “id”: “e3388cbea4e840a”, “name”: “Mary Shelly”, } ] } } Rob Allen @akrabat @rob@akrabat.com
Which to pick? Rob Allen @akrabat @rob@akrabat.com
Lamborghini or Ferrari? Rob Allen @akrabat @rob@akrabat.com
Lamborghini or Truck? Rob Allen @akrabat @rob@akrabat.com
Considerations • What is it to be used for? • Response customisation requirements • HTTP interoperability requirements Rob Allen @akrabat @rob@akrabat.com
API Uses • Do you control both server and client? • How many users are expected? • What is the skill level of your integrators? Rob Allen @akrabat @rob@akrabat.com
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 @rob@akrabat.com
Performance • REST and RPC puts server performance first • GraphQL puts client performance first Rob Allen @akrabat @rob@akrabat.com
Caching • GraphQL and RPC can only cache at application layer • REST can additionally cache at HTTP layer Rob Allen @akrabat @rob@akrabat.com
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 @rob@akrabat.com
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 @rob@akrabat.com
Versioning • RPC, GraphQL and REST can all version via evolution as easily as each other Rob Allen @akrabat @rob@akrabat.com
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 @rob@akrabat.com
Design considerations It’s always hard! Rob Allen @akrabat @rob@akrabat.com
Design considerations It’s always hard! Rob Allen @akrabat @rob@akrabat.com
It’s your choice Rob Allen @akrabat @rob@akrabat.com
Developer Experience Rob Allen @akrabat @rob@akrabat.com
Correctness Rob Allen @akrabat @rob@akrabat.com
Correctness RPC: Functions! Rob Allen @akrabat @rob@akrabat.com
Correctness RPC: Functions! REST: HTTP matters! Rob Allen @akrabat @rob@akrabat.com
Correctness RPC: Functions! REST: HTTP matters! GraphQL: Think in terms of relationships! Rob Allen @akrabat @rob@akrabat.com
Correctness RPC: Functions! REST: HTTP matters! GraphQL: Think in terms of relationships! Rob Allen @akrabat @rob@akrabat.com
Errors Rob Allen @akrabat @rob@akrabat.com
REST Errors HTTP/1.1 503 Service Unavailable Content-Type: application/problem+json Content-Language: en { “status”: 503, “type”: “https://example.com/service-unavailable”, “title”: “Could not authorise user.”, “detail”: “Auth service is down for maintenance.”, “instance”: “https://example.com/maintenance/2023-02-15”, “error_code”: “AUTHSERVICE_UNAVAILABLE” } Rob Allen @akrabat @rob@akrabat.com
GraphQL Errors • • • • • Always returns 200, unless infrastructure failure Common to see cryptic messages from GraphQL service Top level ‘errors’ key for exceptional scenarios Domain errors should be within the schema Consider using an error type per node and a union: union CreateUserResult = UserCreated | UserCreationErrors Rob Allen @akrabat @rob@akrabat.com
Documentation Rob Allen @akrabat @rob@akrabat.com
API Reference • GraphQL: Built-in introspection • REST: OpenAPI Specification Both allow generation of a API reference website. Rob Allen @akrabat @rob@akrabat.com
Tutorials The API Reference is the what, the tutorials are the how and why Rob Allen @akrabat @rob@akrabat.com
GitHub GraphQL Rob Allen @akrabat @rob@akrabat.com
GitHub REST Rob Allen @akrabat @rob@akrabat.com
To sum up Rob Allen @akrabat @rob@akrabat.com
If you suck at providing a REST API, you will suck at providing a GraphQL API Arnaud Lauret, API Handyman Rob Allen @akrabat @rob@akrabat.com
Thank you! Rob Allen @akrabat @rob@akrabat.com
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 @rob@akrabat.com
When you’ve been tasked with creating an HTTP API, the fundamental decision you need to make is which architcture to choose. Should your API be RESTful? What about GraphQL? or (g)RPC? In this session, I’ll explain the choices and how each works. We’ll then look at their strengths and weaknesses in order to help guide your decision. By the end of this talk, you’ll be well-placed to choose the right API architecture for your project.