Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Improving API Communication using GraphQL - Symfony Live 2017

Improving API Communication using GraphQL - Symfony Live 2017

15bd509601f2b058f9d6edf45302dff0?s=128

Pascal de Vink

October 20, 2017
Tweet

Transcript

  1. Improving API communication Using GraphQL

  2. Pascal de Vink Organiser at AmsterdamPHP So8ware engineer at TicketSwap

    Worked with SOAP, REST and GraphQL in producBon @pascaldevink
  3. Why should we care about improving APIs? A liFle history…

  4. First, there was SOAP

  5. 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>
  6. 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> 'encodingStyle' indicates any canonicalization conventions followed in the contents of the containing element. For example, the value 'http://schemas.xmlsoap.org/soap/encoding/' indicates the pattern described in SOAP specification </xs:documentation> </xs:annotation> <xs:list itemType="xs:anyURI"/> </xs:simpleType> <xs:attribute name="encodingStyle" type="tns:encodingStyle"/> <xs:attributeGroup name="encodingStyle"> <xs:attribute ref="tns:encodingStyle"/> </xs:attributeGroup> <xs:element name="Fault" type="tns:Fault"/> <xs:complexType name="Fault" final="extension"> <xs:annotation> <xs:documentation>Fault reporting structure</xs:documentation> </xs:annotation> <xs:sequence> <xs:element name="faultcode" type="xs:QName"/> <xs:element name="faultstring" type="xs:string"/> <xs:element name="faultactor" type="xs:anyURI" minOccurs="0"/> <xs:element name="detail" type="tns:detail" minOccurs="0"/> </xs:sequence> </xs:complexType> <xs:complexType name="detail"> <xs:sequence> <xs:any namespace="##any" minOccurs="0" maxOccurs="unbounded" processContents="lax"/> </xs:sequence> <xs:anyAttribute namespace="##any" processContents="lax"/> </xs:complexType> </xs:schema>
  7. None
  8. SOAP had advantages • Language neutral: can be implemented in

    any language • Pla.orm independent: can be executed on any plaKorm • Rela5vely simple: uses XML, which is easy to read and write • Scalable: uses HTTP protocol, which allows underlying plaKorms to easily scale
  9. 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
  10. Then came REST(fullness) Roy Fielding here

  11. REST was easier • Much simpler: by reusing HTTP verbs

    and using JSON • Easy resource discovery: every resource has a unique URL
  12. 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
  13. To compensate, HATEOS was added

  14. HATEOS fixed things • Discoverability: by reusing HTTP verbs and

    using JSON • Availability based on state: truly unique URL for every resource
  15. 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
  16. 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" } }
  17. 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" } }
  18. 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" } }
  19. 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" } }
  20. In use by Facebook since 2012 An open standard since

    2015 hIp:/ /graphql.org
  21. Also adopted by

  22. Powerful type system • Int • Float • String •

    Boolean • ID • Custom scalar • Enum • Union • List • Non-null • Interface • (Input) Object
  23. Get what you need

  24. Get many resources

  25. Developer tools

  26. Developer tools

  27. Developer tools

  28. Development libraries • Javascript • Ruby • PHP • Python

    • Java • C/C++ • Go • Scala • .Net • SwiZ • Elixir • Haskell • Lua • Elm • Clojure • ClojureScript • OCaml • Rust • R • SQL
  29. Demo time

  30. In PHP

  31. $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']; } ], ], ]);
  32. $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);
  33. In Symfony

  34. composer config extra.symfony.allow-contrib true composer require overblog/graphql-bundle

  35. Query: type: object config: description: A test query. fields: echo:

    type: String args: message: type: String resolve: "@=resolver('echo', [args])"
  36. Query: type: object config: description: A test query. fields: echo:

    type: String args: message: type: String resolve: "@=resolver('echo', [args])"
  37. Query: type: object config: description: A test query. fields: echo:

    type: String args: message: type: String resolve: "@=resolver('echo', [args])"
  38. <?php namespace App\Resolver; class EchoWell { public function resolve($input) {

    return $input['message']; } }
  39. services: App\Resolver\EchoWell: tags: - { name: overblog_graphql.resolver, method: resolve, alias:

    echo }
  40. services: App\Resolver\EchoWell: tags: - { name: overblog_graphql.resolver, method: resolve, alias:

    echo }
  41. Query: type: object config: description: A test query. fields: echo:

    type: String args: message: type: String resolve: "@=resolver('echo', [args])"
  42. Symfony demo time

  43. Our first implementation…

  44. None
  45. None
  46. Relay

  47. • Node • Viewer Required fields

  48. Query: type: object config: fields: node: builder: Node builderConfig: nodeInterfaceType:

    Node idFetcher: '@=service("node_resolver").resolveByGlobalId(value, request)'
  49. { node(id: "MDEwOlJlcG9zaXRvcnk0NTQ2MjQ3Nw==") { ... on Organization { name }

    ... on Repository { name } } }
  50. Globally unique identifier City:12345 Q2l0eToxMjM0NQ== Type name Identifier

  51. { viewer { name starredRepositories { name } } }

  52. Connections • Connec5on: relaBons between nodes are called connecBons •

    Edge: the relaBonship between two nodes • Node: a specific data item
  53. { viewer { firstname lastname starredRepositories(first:5) { pageInfo { hasNextPage

    } edges { cursor node { # Node details } } } } }
  54. { viewer { firstname lastname starredRepositories(first:5) { pageInfo { hasNextPage

    } edges { cursor node { # Node details } } } } }
  55. { viewer { firstname lastname starredRepositories(first:5) { pageInfo { hasNextPage

    } edges { cursor node { # Node details } } } } }
  56. { viewer { firstname lastname starredRepositories(first:5) { pageInfo { hasNextPage

    } edges { cursor node { # Node details } } } } }
  57. Complexity overload

  58. Query Complexity Analysis overblog_graphql: security: query_max_complexity: 110 Query: type: object

    config: description: A test query. fields: echo: type: String complexity: '@=100 + childrenComplexity' args: message: type: String resolve: "@=resolver('echo', [args])"
  59. Limiting Query Depth overblog_graphql: security: query_max_depth: 10

  60. N+1 problem

  61. Uses batching and caching to efficiently load resources Data loader

  62. public function resolve($args) { return $this->dataLoader->load($args['id']); }

  63. None
  64. public function load(array $ids) : Promise { $nodes = //

    Find all nodes by id $nodeValues = // Map them to returnable values return $this->promiseAdapter->createFulfilled($eventValues); }
  65. Look at: • hIps:/ /github.com/facebook/dataloader • hIps:/ /github.com/overblog/dataloader-php Data loader

  66. Mutations

  67. Mutation: type: object config: fields: sum: type: Int args: x:

    Int y: Int resolve: "@=mutation('sum', [args])"
  68. <?php namespace App\Resolver; class Sum { public function mutate($args) {

    return $args['x'] + $args['y']; } }
  69. services: App\Resolver\Sum: tags: - { name: overblog_graphql.mutation, method: mutate, alias:

    sum }
  70. Deprecating fields

  71. originalPrice: type: 'Money!' description: 'The original price of the tickets.'

    deprecationReason: | Use the originalPrice field inside the price value instead.
  72. None
  73. Monitoring

  74. None
  75. None
  76. None
  77. None
  78. 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
  79. 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
  80. Resources • hFp:/ /graphql.org • hFps:/ /github.com/chentsulin/awesome-graphql • hFps:/ /www.learnrelay.org

    • hFps:/ /github.com/overblog/GraphQLBundle
  81. Thank you @pascaldevink hIps:/ /joind.in/talk/41b30