Share
Embed
Improving API communication using GraphQL
Pascal de Vink
June 16, 2017
Transcript
73 slides
-
Who am I to say anything about it? Currently working
at TicketSwap Worked with SOAP, REST and GraphQL in producBon -
Remember SOAP? <?xml version="1.0"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2001/12/soap-envelope" SOAP-ENV:encodingStyle="http://www.w3.org/2001/12/soap-encoding"> <SOAP-ENV:Body xmlns:m="http://www.xyz.org/quotations"> <m:GetQuotation>
<m:QuotationsName>Microsoft</m:QuotationsName> </m:GetQuotation> </SOAP-ENV:Body> </SOAP-ENV:Envelope> -
Remember SOAP? <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://schemas.xmlsoap.org/soap/envelope/" targetNamespace="http://schemas.xmlsoap.org/soap/envelope/"> <xs:element name="Envelope" type="tns:Envelope"/> <xs:complexType
name="Envelope"> <xs:sequence> <xs:element ref="tns:Header" minOccurs="0"/> <xs:element ref="tns:Body" minOccurs="1"/> <xs:any namespace="##other" minOccurs="0" maxOccurs="unbounded" processContents="lax"/> </xs:sequence> <xs:anyAttribute namespace="##other" processContents="lax"/> </xs:complexType> <xs:element name="Header" type="tns:Header"/> <xs:complexType name="Header"> <xs:sequence> <xs:any namespace="##other" minOccurs="0" maxOccurs="unbounded" processContents="lax"/> </xs:sequence> <xs:anyAttribute namespace="##other" processContents="lax"/> </xs:complexType> <xs:element name="Body" type="tns:Body"/> <xs:complexType name="Body"> <xs:sequence> <xs:any namespace="##any" minOccurs="0" maxOccurs="unbounded" processContents="lax"/> </xs:sequence> <xs:anyAttribute namespace="##any" processContents="lax"> <xs:annotation> <xs:documentation> Prose in the spec does not specify that attributes are allowed on the Body element </xs:documentation> </xs:annotation> </xs:anyAttribute> </xs:complexType> <xs:attribute name="mustUnderstand"> <xs:simpleType> <xs:restriction base="xs:boolean"> <xs:pattern value="0|1"/> </xs:restriction> </xs:simpleType> </xs:attribute> <xs:attribute name="actor" type="xs:anyURI"/> <xs:simpleType name="encodingStyle"> <xs:annotation> <xs:documentation> -
SOAP had advantages • Language neutral: can be implemented in
any language • Pla.orm independent: can be executed on any plaHorm • Rela5vely simple: uses XML, which is easy to read and write • Scalable: uses HTTP protocol, which allows underlying plaHorms to easily scale -
And some disadvantages • Slooooooooow: uses XML, which is was
slow to parse, and debug • Depends on WSDL: needs a meta service to describe it’s uses, but is tedious to write • XML is hard to read: manually reading a soap envelop and response is not for the faint of hart -
REST was easier • Much simpler: by reusing HTTP verbs
and using JSON • Easy resource discovery: every resource has a unique URL -
But left some parts out • Resource discovery was s5ll
hard: a client had to know the url • Redundant and unneeded informa5on: a resource exposes everything it knows, always • No batching support: 1 request for each resource • Limited by HTTP verbs: not all mutaBons can be mapped -
HATEOS fixed things • Discoverability: by reusing HTTP verbs and
using JSON • Availability based on state: every resource has a unique URL -
But was hard • Hard to work with: it’s not
easy to read all those _link fields • LiIle tooling support: discovery must be done manually • Focus on long-term design: which most people are not very good at • Lots of requests: as each link sBll requires a new request -
Enter GraphQL Describe your data Ask for what you want
Get predictable results type Project { name: String tagline: String contributors: [User] } { project(name: "GraphQL") { tagline } } { "project": { "tagline": "A query language for APIs" } } -
Enter GraphQL Describe your data Ask for what you want
Get predictable results type Project { name: String tagline: String contributors: [User] } { project(name: "GraphQL") { tagline } } { "project": { "tagline": "A query language for APIs" } } -
Enter GraphQL Describe your data Ask for what you want
Get predictable results type Project { name: String tagline: String contributors: [User] } { project(name: "GraphQL") { tagline } } { "project": { "tagline": "A query language for APIs" } } -
Enter GraphQL Describe your data Ask for what you want
Get predictable results type Project { name: String tagline: String contributors: [User] } { project(name: "GraphQL") { tagline } } { "project": { "tagline": "A query language for APIs" } } -
In use by Facebook since 2012 An open standard since
2015 hIp:/ /graphql.org -
Powerful type system • Int • Float • String •
Boolean • ID • Custom scalar • Enum • Union • List • Non-null • Interface • (Input) Object -
Development libraries • Javascript • Ruby • PHP • Python
• Java • C/C++ • Go • Scala • .Net • SwiZ • Elixir • Haskell • Lua • Elm • Clojure • ClojureScript • OCaml • Rust • R • SQL -
$queryType = new \GraphQL\Type\Definition\ObjectType([ 'name' => 'Query', 'fields' => [
'echo' => [ 'type' => \GraphQL\Type\Definition\Type::string(), 'args' => [ 'message' => ['type' => \GraphQL\Type\Definition\Type::string()], ], 'resolve' => function ($root, $args) { return $root['prefix'].$args['message']; } ], ], ]); -
$queryType = new \GraphQL\Type\Definition\ObjectType([ 'name' => 'Query', 'fields' => [
'echo' => [ 'type' => \GraphQL\Type\Definition\Type::string(), 'args' => [ 'message' => ['type' => \GraphQL\Type\Definition\Type::string()], ], 'resolve' => function ($root, $args) { return $root['prefix'].$args['message']; } ], ], ]); -
$queryType = new \GraphQL\Type\Definition\ObjectType([ 'name' => 'Query', 'fields' => [
'echo' => [ 'type' => \GraphQL\Type\Definition\Type::string(), 'args' => [ 'message' => ['type' => \GraphQL\Type\Definition\Type::string()], ], 'resolve' => function ($root, $args) { return $root['prefix'].$args['message']; } ], ], ]); -
$queryType = new \GraphQL\Type\Definition\ObjectType([ 'name' => 'Query', 'fields' => [
'echo' => [ 'type' => \GraphQL\Type\Definition\Type::string(), 'args' => [ 'message' => ['type' => \GraphQL\Type\Definition\Type::string()], ], 'resolve' => function ($root, $args) { return $root['prefix'].$args['message']; } ], ], ]); -
$queryType = new \GraphQL\Type\Definition\ObjectType([ 'name' => 'Query', 'fields' => [
'echo' => [ 'type' => \GraphQL\Type\Definition\Type::string(), 'args' => [ 'message' => ['type' => \GraphQL\Type\Definition\Type::string()], ], 'resolve' => function ($root, $args) { return $root['prefix'].$args['message']; } ], ], ]); -
$schema = new Schema([ 'query' => $queryType, ]); $rawInput =
file_get_contents('php://input'); $input = json_decode($rawInput, true); $query = $input['query']; $variableValues = isset($input['variables']) ? $input['variables'] : null; $rootValue = ['prefix' => 'You said: ']; $result = GraphQL::execute($schema, $query, $rootValue, null, $variableValues); header('Content-Type: application/json; charset=UTF-8'); echo json_encode($result); -
$schema = new Schema([ 'query' => $queryType, ]); $rawInput =
file_get_contents('php://input'); $input = json_decode($rawInput, true); $query = $input['query']; $variableValues = isset($input['variables']) ? $input['variables'] : null; $rootValue = ['prefix' => 'You said: ']; $result = GraphQL::execute($schema, $query, $rootValue, null, $variableValues); header('Content-Type: application/json; charset=UTF-8'); echo json_encode($result); -
$schema = new Schema([ 'query' => $queryType, ]); $rawInput =
file_get_contents('php://input'); $input = json_decode($rawInput, true); $query = $input['query']; $variableValues = isset($input['variables']) ? $input['variables'] : null; $rootValue = ['prefix' => 'You said: ']; $result = GraphQL::execute($schema, $query, $rootValue, null, $variableValues); header('Content-Type: application/json; charset=UTF-8'); echo json_encode($result); -
$schema = new Schema([ 'query' => $queryType, ]); $rawInput =
file_get_contents('php://input'); $input = json_decode($rawInput, true); $query = $input['query']; $variableValues = isset($input['variables']) ? $input['variables'] : null; $rootValue = ['prefix' => 'You said: ']; $result = GraphQL::execute($schema, $query, $rootValue, null, $variableValues); header('Content-Type: application/json; charset=UTF-8'); echo json_encode($result); -
$schema = new Schema([ 'query' => $queryType, ]); $rawInput =
file_get_contents('php://input'); $input = json_decode($rawInput, true); $query = $input['query']; $variableValues = isset($input['variables']) ? $input['variables'] : null; $rootValue = ['prefix' => 'You said: ']; $result = GraphQL::execute($schema, $query, $rootValue, null, $variableValues); header('Content-Type: application/json; charset=UTF-8'); echo json_encode($result); -
$queryType = new \GraphQL\Type\Definition\ObjectType([ 'name' => 'Query', 'fields' => [
'node' => [ 'type' => \GraphQL\Type\Definition\Type::listOf($nodeInterface), 'args' => [ 'id' => ['type' => \GraphQL\Type\Definition\Type::id()], ], 'resolve' => function ($root, $args) { // Resolve the node by ID } ], ], ]); -
{ node(id: "Q2l0eTox") { ... on City { name }
... on Location { name } } } -
Connections • Connec5on: relaBons between models are called connecBons •
Node: a specific data item • Edge: the relaBonship between two nodes -
{ viewer { firstname lastname wantedListings(first:5) { pageInfo { hasNextPage
} edges { cursor node { # Node details } } } } } -
{ viewer { firstname lastname wantedListings(first:5) { pageInfo { hasNextPage
} edges { cursor node { # Node details } } } } } -
{ viewer { firstname lastname wantedListings(first:5) { pageInfo { hasNextPage
} edges { cursor node { # Node details } } } } } -
{ viewer { firstname lastname wantedListings(first:5) { pageInfo { hasNextPage
} edges { cursor node { # Node details } } } } } -
Query Complexity Analysis use GraphQL\GraphQL; /** @var \GraphQL\Validator\Rules\QueryComplexity $queryComplexity */
$queryComplexity = DocumentValidator::getRule('QueryComplexity'); $queryComplexity->setMaxQueryComplexity($maxQueryComplexity = 110); GraphQL::execute(/*...*/); -
Limiting Query Depth use GraphQL\GraphQL; /** @var \GraphQL\Validator\Rules\QueryDepth $queryDepth */
$queryDepth = DocumentValidator::getRule('QueryDepth'); $queryDepth->setMaxQueryDepth($maxQueryDepth = 10); GraphQL::execute(/*...*/); -
public function load(array $ids) : Promise { $nodes = //
Find all nodes by id $nodeValues = // Map them to returnable values return $this->promiseAdapter->createFulfilled($eventValues); } -
$mutationType = new \GraphQL\Type\Definition\ObjectType( [ 'name' => 'Calc', 'fields' =>
[ 'sum' => [ 'type' => \GraphQl\Type\Definition\Type::int(), 'args' => [ 'x' => ['type' => \GraphQl\Type\Definition\Type::int()], 'y' => ['type' => \GraphQl\Type\Definition\Type::int()], ], 'resolve' => function ($root, $args) { return $args['x'] + $args['y']; }, ], ], ] ); -
$mutationType = new \GraphQL\Type\Definition\ObjectType( [ 'name' => 'Calc', 'fields' =>
[ 'sum' => [ 'type' => \GraphQl\Type\Definition\Type::int(), 'args' => [ 'x' => ['type' => \GraphQl\Type\Definition\Type::int()], 'y' => ['type' => \GraphQl\Type\Definition\Type::int()], ], 'resolve' => function ($root, $args) { return $args['x'] + $args['y']; }, ], ], ] ); -
$mutationType = new \GraphQL\Type\Definition\ObjectType( [ 'name' => ‘Mutation', 'fields' =>
[ 'sum' => [ 'type' => \GraphQl\Type\Definition\Type::int(), 'args' => [ 'x' => ['type' => \GraphQl\Type\Definition\Type::int()], 'y' => ['type' => \GraphQl\Type\Definition\Type::int()], ], 'resolve' => function ($root, $args) { return $args['x'] + $args['y']; }, ], ], ] ); -
$mutationType = new \GraphQL\Type\Definition\ObjectType( [ 'name' => ‘Mutation', 'fields' =>
[ 'sum' => [ 'type' => \GraphQl\Type\Definition\Type::int(), 'args' => [ 'x' => ['type' => \GraphQl\Type\Definition\Type::int()], 'y' => ['type' => \GraphQl\Type\Definition\Type::int()], ], 'resolve' => function ($root, $args) { return $args['x'] + $args['y']; }, ], ], ] ); -
$mutationType = new \GraphQL\Type\Definition\ObjectType( [ 'name' => ‘Mutation', 'fields' =>
[ 'sum' => [ 'type' => \GraphQl\Type\Definition\Type::int(), 'args' => [ 'x' => ['type' => \GraphQl\Type\Definition\Type::int()], 'y' => ['type' => \GraphQl\Type\Definition\Type::int()], ], 'resolve' => function ($root, $args) { return $args['x'] + $args['y']; }, ], ], ] ); -
Conclusion • Hard to master: new developers might not be
familiar with it • S5ll rough around the edges: libraries and frameworks are sBll at their infancy • Deep complexity problems: can be hard to find and solve -
Conclusion • Easy discovery: fun and easy to work with
• Powerful tools: from developer tools to libraries and frameworks • Lightweight communica5on: using only what you need reduces overhead • Inline documenta5on: no more separate documentaBon to ship