GRAPHQL
ABOUT ME Bernd Alter CTO of VOTUM GmbH bernd.alter@votum.de @bazoo0815
AGENDA Basic introduction to GraphQL Schema definition Queries & Mutations GraphiQL - the GraphQL GUI Keyset pagination Experiences, tips & tricks
DEFINITION GraphQL vs Relay vs React React = open-source JS library for building user interfaces Relay = client-side application framework based on React GraphQL = server-side query language implementation
GraphQL server Data sourcesVarious client apps JSON payload GraphQL queries WHAT IS GRAPHQL? query language created by Facebook 2012 open-source'd in 2015
KEY FEATURES strongly typed objects and fields highly customizable queries nested/parallel objects in a single request no versioning necessary data aggregation from multiple sources integrated GUI with auto-completion & error check
GRAPHQL IN SYMFONY several bundles available overblog/GraphQLBundle suribit/GraphQLBundle Youshido/GraphQLBundle ... (see links) composer require overblog/GraphQLBundle # also installs base bundle `webonyx/graphql-php`
GRAPHQL SCHEMA Type (for fields/attributes) Builder Connection Edges Node Cursor Union Interface Resolver
SIMPLE TYPES Type Example(s) Int 1 25 432 Float 1.23 7.6 String "Any string" Boolean true false Enum 0 (=unknown) 1 (=male) 2 (=female)
SIMPLE TYPES (EXAMPLE) Article: type: object fields: id: type: Int! <- Exclamation mark = required/not- null headline: type: String! description: "A description can be added anywhere" featured: type: Boolean
SPECIAL TYPE: ID global identifier base64-encoded string unique application-wide(!) Article: type: object fields: id: type: ID! (instead of Int!) Example: "YXJ0aWNsZToxMjY=" -> "article:126"
BUILDER reusable and configurable 'templates' or 'functions' field builder (builder) argument builder (argsBuilder)
FIELD BUILDER (EXAMPLE) #app/config/config.yml overblog_graphql: #... definitions: #... builders: field: - alias: "RawId" class: "MyBundleGraphQLFieldRawIdField" <?php class RawIdField implements MappingInterface { public function toMappingDefinition(array $config) { $name = $config[ 'name'] ?? 'id'; $type = $config[ 'type'] ?? 'Int!'; return [ 'description' => 'The raw ID of an object' , 'type' => $type, 'resolve' => '@=value.' . name, ]; } } # in schema definition Article: type: object fields: rawId: builder: RawId ... Video: type: object fields: rawId: builder: RawId builderConfig: name: idVideo type: String ...
NESTING OF OBJECTS Article: type: object fields: id: type: ID! ... artists: type: "[Artist]" images: type: "[Image]" Artist: type: object fields: id: type: ID! name: type: String! Image: type: object fields: id: type: ID! url: type: String!
CONNECTIONS lists of objects used for cursor-based (or keyset) pagination can have arguments (for filtering, sorting, aggregation) connection: { edges: [ { node: { id ... } } ], pageInfo: { hasNextPage hasPreviousPage startCursor endCursor } }
CONNECTIONS (EXAMPLE) ArticleConnection : type: relay-connection config: nodeType: Article allArticles: type: ArticleConnection argsBuilder: ConnectionArgs args: artist: type: Int description: "Artist id, e.g. 525338 for artist 'Rihanna'" dateFrom: type: String description: "Filter connection by date (greater than or equal)" dateTo: type: String description: "Filter connection by date (lower than or equal)" resolve: "@=resolver('article_connection', [args])"
UNIONS Use it to ... ... handle multiple object types in one field ... deal with heterogeneous structures
UNIONS: EXAMPLE Schema/Query Content: type: union config: types: [Article,Product] Article: type: object fields: headline: type: String! Product: type: object fields: title: type: String! query Root { content(artist: "Rihanna") { ... on Article { __typename id headline } ... on Product { __typename id title } } }
UNIONS: EXAMPLE Response { "data": { "content": [ { "__typename": "Product", "id": "QXJ0aWNsZToyMzc4OTI=" , "title": "Unapologetic" }, { "__typename": "Article", "id": "QXJ0aWNsZToyMzgyMjg=" , "headline": "ECHO 2017: Rihanna ist fÃŒr den deutschen Musikpreis nominiert" }, { "__typename": "Product", "id": "QXJ0aWNsZToyMzgyMjU=" , "title": "ANTI" }, { "__typename": "Product", "id": "QXJ0aWNsZToyMzgyMjY=" , "title": "Diamonds" } ] } }
INTERFACES just like in PHP objects can have multiple interfaces not cascading object can implement interfaces interface cannot implement interfaces SeoContent: type: interface config: fields: seoKeywords: type: String Article: type: object config: interfaces: [SeoContent, Node] fields: id: type: ID! headline: type: String! seoKeywords: type: String # NOT POSSIBLE OtherInterface: type: interface config: interfaces: [SeoContent]
RESOLVER connecting objects/fields to methods # schema definition in Query.types.yml Query: type: object config: fields: articles: type: ArticleConnection args: type: type: ArticleType artist: type: Int resolve: "@=resolver('article_connection', [args])"
RESOLVER # config in services.yml services: my.graphql.resolver.content: class: MyGraphQlBundle ResolverContentResolver tags: - { name: overblog_graphql.resolver, alias: "root_query", method: "resolveRootQuery" } - { name: overblog_graphql.resolver, alias: "article", method: "resolveArticle" } - { name: overblog_graphql.resolver, alias: "article_connection" , method: "resolveArticleConnection" } # in class MyGraphQlBundleResolverContentResolver <?php ... /** * @param Argument $argument * @return ArticleConnectionObject */ public function resolveArticleConnection (Argument $argument) { if (isset($argument['artist']) { $filters[] = $this->createArtistFilter($argument); } /** other code ... */ return $this->getArticleConnection($filters); }
REQUESTS Queries Mutations Fragments Variables
QUERY read-only request highly customizable nesting of objects any combination of fields multiple resources in one request query Root { article(id: 123) { id headline } videos(first: 5) { title url } artist(name: "Rihanna") { name articles( first: 2, sortBy: "date") headline } } }
MUTATION data changing request requires payload object # Request mutation myLogin($input: CredentialsInput!) { loginUser(input: $input) { userName loginSuccessful } } # Payload (variables) { "input": { "email": "me@email.com" , "password": "123456", } } # Response { "data": { "loginEmailUser" : { "userName": "Max", "loginSuccessful" : true } } }
FRAGMENTS reusable request pieces nesting allowed beware of (infinite) loops! query Root { article(id: 123) { ...articleFragment } artist(name: "Rihanna") { name articles( first: 2, sortBy: "date") ...articleFragment } } } fragment articleFragment on Article { id headline } fragment artistFragment on Artist { name articles { ...articleFragment } } fragment articleFragment on Article { id headline artist { ...artistFragment } }
VARIABLES needed for payload objects (see mutations) reusable values/parameters
GRAPHIQL built-in GUI available in dev- mode under auto-generated docs sidebar good demo: /graphiql swapi- graphql
GRAPHIQL DEMO!
KEYSET PAGINATION WHAT IS IT ABOUT? usually offset is used overhead of read-ahead using cursor containing required information only read what you need # OFFSET SELECT * FROM articles ORDER BY date DESC, id DESC LIMIT 10 OFFSET 50; # KEYSET # cursor: date=2017-03-29, id=165 SELECT * FROM articles WHERE date < :cursor_date OR (date = :cursor_date AND id < :cursor_id) ORDER BY date DESC, id DESC LIMIT 10;
KEYSET PAGINATION OFFSET VS KEYSET Source: http://blog.novatec-gmbh.de/art-pagination-offset-vs-value-based-paging/
KEYSET PAGINATION Advantages Disadvantages 'real' pagination no page browsing (only previous and next) better performance hard(er) to implement good for endless scrolling
PROBLEMS / PITFALLS resolvers are atomic agnostic to parent/child objects (potential) need to forward settings loss of control about queries can lead to performance problems danger of cyclic/recursive queries
NOTES, TIPS & TRICKS possible to wrap (existing) REST API in GraphQL prepare application to deal with pre-flight request (CORS, HTTP method OPTIONS) add caching (on object level) filenames for type definitions have to end with .types.yml use aliases for usage of same object/field in query query Root { article(artist: "Rihanna") { id headline } otherArticle: article(artist: "Kasabian") { id headline } }
LINKS: DOCUMENTATION Official website: Dra RFC Specification (10/2016) GraphQL: A data query language GraphQL Schema Language Cheat Sheet Connections explained: http://graphql.org/ http://facebook.github.io/graphql/ https://code.facebook.com/posts/1691455094417024 https://wehavefaces.net/graphql-shorthand-notation-cheatsheet- 17cd715861b6 https://dev-blog.apollodata.com/explaining-graphql-connections- c48b7c3d6976
LINKS: SYMFONY & OTHER IMPLEMENTATIONS Reference implementation in PHP: Symfony bundle: GraphQL with PHP and the Symfony Framework: List of implementations for various languages: https://github.com/webonyx/graphql-php overblog/GraphQLBundle https://www.symfony.fi/entry/graphql-with-php-and-the-symfony- framework https://github.com/chentsulin/awesome-graphql
LINKS: VIDEOS by Lee Byron by Steven Luscher Exploring GraphQL at react-europe 2015 Zero to GraphQL in 30 Minutes
LINKS: KEYSET PAGINATION Slides: Pagination done the right way (by Markus Winand): The art of pagination - Offset vs. value based paging: https://www.slideshare.net/MarkusWinand/p2d2- pagination-done-the-postgresql-way http://blog.novatec-gmbh.de/art-pagination-offset-vs-value- based-paging/
THANKS FOR LISTENING

GraphQL in Symfony

  • 1.
  • 2.
    ABOUT ME Bernd Alter CTOof VOTUM GmbH bernd.alter@votum.de @bazoo0815
  • 3.
    AGENDA Basic introduction toGraphQL Schema definition Queries & Mutations GraphiQL - the GraphQL GUI Keyset pagination Experiences, tips & tricks
  • 4.
    DEFINITION GraphQL vs Relayvs React React = open-source JS library for building user interfaces Relay = client-side application framework based on React GraphQL = server-side query language implementation
  • 5.
    GraphQL server DatasourcesVarious client apps JSON payload GraphQL queries WHAT IS GRAPHQL? query language created by Facebook 2012 open-source'd in 2015
  • 6.
    KEY FEATURES strongly typedobjects and fields highly customizable queries nested/parallel objects in a single request no versioning necessary data aggregation from multiple sources integrated GUI with auto-completion & error check
  • 7.
    GRAPHQL IN SYMFONY severalbundles available overblog/GraphQLBundle suribit/GraphQLBundle Youshido/GraphQLBundle ... (see links) composer require overblog/GraphQLBundle # also installs base bundle `webonyx/graphql-php`
  • 8.
    GRAPHQL SCHEMA Type (forfields/attributes) Builder Connection Edges Node Cursor Union Interface Resolver
  • 9.
    SIMPLE TYPES Type Example(s) Int1 25 432 Float 1.23 7.6 String "Any string" Boolean true false Enum 0 (=unknown) 1 (=male) 2 (=female)
  • 10.
    SIMPLE TYPES (EXAMPLE) Article: type:object fields: id: type: Int! <- Exclamation mark = required/not- null headline: type: String! description: "A description can be added anywhere" featured: type: Boolean
  • 11.
    SPECIAL TYPE: ID globalidentifier base64-encoded string unique application-wide(!) Article: type: object fields: id: type: ID! (instead of Int!) Example: "YXJ0aWNsZToxMjY=" -> "article:126"
  • 12.
    BUILDER reusable and configurable'templates' or 'functions' field builder (builder) argument builder (argsBuilder)
  • 13.
    FIELD BUILDER (EXAMPLE) #app/config/config.yml overblog_graphql: #... definitions: #... builders: field: -alias: "RawId" class: "MyBundleGraphQLFieldRawIdField" <?php class RawIdField implements MappingInterface { public function toMappingDefinition(array $config) { $name = $config[ 'name'] ?? 'id'; $type = $config[ 'type'] ?? 'Int!'; return [ 'description' => 'The raw ID of an object' , 'type' => $type, 'resolve' => '@=value.' . name, ]; } } # in schema definition Article: type: object fields: rawId: builder: RawId ... Video: type: object fields: rawId: builder: RawId builderConfig: name: idVideo type: String ...
  • 14.
    NESTING OF OBJECTS Article: type:object fields: id: type: ID! ... artists: type: "[Artist]" images: type: "[Image]" Artist: type: object fields: id: type: ID! name: type: String! Image: type: object fields: id: type: ID! url: type: String!
  • 15.
    CONNECTIONS lists of objects usedfor cursor-based (or keyset) pagination can have arguments (for filtering, sorting, aggregation) connection: { edges: [ { node: { id ... } } ], pageInfo: { hasNextPage hasPreviousPage startCursor endCursor } }
  • 16.
    CONNECTIONS (EXAMPLE) ArticleConnection : type:relay-connection config: nodeType: Article allArticles: type: ArticleConnection argsBuilder: ConnectionArgs args: artist: type: Int description: "Artist id, e.g. 525338 for artist 'Rihanna'" dateFrom: type: String description: "Filter connection by date (greater than or equal)" dateTo: type: String description: "Filter connection by date (lower than or equal)" resolve: "@=resolver('article_connection', [args])"
  • 17.
    UNIONS Use it to... ... handle multiple object types in one field ... deal with heterogeneous structures
  • 18.
    UNIONS: EXAMPLE Schema/Query Content: type: union config: types:[Article,Product] Article: type: object fields: headline: type: String! Product: type: object fields: title: type: String! query Root { content(artist: "Rihanna") { ... on Article { __typename id headline } ... on Product { __typename id title } } }
  • 19.
    UNIONS: EXAMPLE Response { "data": { "content":[ { "__typename": "Product", "id": "QXJ0aWNsZToyMzc4OTI=" , "title": "Unapologetic" }, { "__typename": "Article", "id": "QXJ0aWNsZToyMzgyMjg=" , "headline": "ECHO 2017: Rihanna ist fÃŒr den deutschen Musikpreis nominiert" }, { "__typename": "Product", "id": "QXJ0aWNsZToyMzgyMjU=" , "title": "ANTI" }, { "__typename": "Product", "id": "QXJ0aWNsZToyMzgyMjY=" , "title": "Diamonds" } ] } }
  • 20.
    INTERFACES just like inPHP objects can have multiple interfaces not cascading object can implement interfaces interface cannot implement interfaces SeoContent: type: interface config: fields: seoKeywords: type: String Article: type: object config: interfaces: [SeoContent, Node] fields: id: type: ID! headline: type: String! seoKeywords: type: String # NOT POSSIBLE OtherInterface: type: interface config: interfaces: [SeoContent]
  • 21.
    RESOLVER connecting objects/fields tomethods # schema definition in Query.types.yml Query: type: object config: fields: articles: type: ArticleConnection args: type: type: ArticleType artist: type: Int resolve: "@=resolver('article_connection', [args])"
  • 22.
    RESOLVER # config inservices.yml services: my.graphql.resolver.content: class: MyGraphQlBundle ResolverContentResolver tags: - { name: overblog_graphql.resolver, alias: "root_query", method: "resolveRootQuery" } - { name: overblog_graphql.resolver, alias: "article", method: "resolveArticle" } - { name: overblog_graphql.resolver, alias: "article_connection" , method: "resolveArticleConnection" } # in class MyGraphQlBundleResolverContentResolver <?php ... /** * @param Argument $argument * @return ArticleConnectionObject */ public function resolveArticleConnection (Argument $argument) { if (isset($argument['artist']) { $filters[] = $this->createArtistFilter($argument); } /** other code ... */ return $this->getArticleConnection($filters); }
  • 23.
  • 24.
    QUERY read-only request highly customizable nestingof objects any combination of fields multiple resources in one request query Root { article(id: 123) { id headline } videos(first: 5) { title url } artist(name: "Rihanna") { name articles( first: 2, sortBy: "date") headline } } }
  • 25.
    MUTATION data changing request requirespayload object # Request mutation myLogin($input: CredentialsInput!) { loginUser(input: $input) { userName loginSuccessful } } # Payload (variables) { "input": { "email": "me@email.com" , "password": "123456", } } # Response { "data": { "loginEmailUser" : { "userName": "Max", "loginSuccessful" : true } } }
  • 26.
    FRAGMENTS reusable request pieces nestingallowed beware of (infinite) loops! query Root { article(id: 123) { ...articleFragment } artist(name: "Rihanna") { name articles( first: 2, sortBy: "date") ...articleFragment } } } fragment articleFragment on Article { id headline } fragment artistFragment on Artist { name articles { ...articleFragment } } fragment articleFragment on Article { id headline artist { ...artistFragment } }
  • 27.
    VARIABLES needed for payloadobjects (see mutations) reusable values/parameters
  • 28.
    GRAPHIQL built-in GUI available indev- mode under auto-generated docs sidebar good demo: /graphiql swapi- graphql
  • 29.
  • 30.
    KEYSET PAGINATION WHAT ISIT ABOUT? usually offset is used overhead of read-ahead using cursor containing required information only read what you need # OFFSET SELECT * FROM articles ORDER BY date DESC, id DESC LIMIT 10 OFFSET 50; # KEYSET # cursor: date=2017-03-29, id=165 SELECT * FROM articles WHERE date < :cursor_date OR (date = :cursor_date AND id < :cursor_id) ORDER BY date DESC, id DESC LIMIT 10;
  • 31.
    KEYSET PAGINATION OFFSET VSKEYSET Source: http://blog.novatec-gmbh.de/art-pagination-offset-vs-value-based-paging/
  • 32.
    KEYSET PAGINATION Advantages Disadvantages 'real'pagination no page browsing (only previous and next) better performance hard(er) to implement good for endless scrolling
  • 33.
    PROBLEMS / PITFALLS resolversare atomic agnostic to parent/child objects (potential) need to forward settings loss of control about queries can lead to performance problems danger of cyclic/recursive queries
  • 34.
    NOTES, TIPS &TRICKS possible to wrap (existing) REST API in GraphQL prepare application to deal with pre-flight request (CORS, HTTP method OPTIONS) add caching (on object level) filenames for type definitions have to end with .types.yml use aliases for usage of same object/field in query query Root { article(artist: "Rihanna") { id headline } otherArticle: article(artist: "Kasabian") { id headline } }
  • 35.
    LINKS: DOCUMENTATION Official website: DraRFC Specification (10/2016) GraphQL: A data query language GraphQL Schema Language Cheat Sheet Connections explained: http://graphql.org/ http://facebook.github.io/graphql/ https://code.facebook.com/posts/1691455094417024 https://wehavefaces.net/graphql-shorthand-notation-cheatsheet- 17cd715861b6 https://dev-blog.apollodata.com/explaining-graphql-connections- c48b7c3d6976
  • 36.
    LINKS: SYMFONY &OTHER IMPLEMENTATIONS Reference implementation in PHP: Symfony bundle: GraphQL with PHP and the Symfony Framework: List of implementations for various languages: https://github.com/webonyx/graphql-php overblog/GraphQLBundle https://www.symfony.fi/entry/graphql-with-php-and-the-symfony- framework https://github.com/chentsulin/awesome-graphql
  • 37.
    LINKS: VIDEOS by LeeByron by Steven Luscher Exploring GraphQL at react-europe 2015 Zero to GraphQL in 30 Minutes
  • 38.
    LINKS: KEYSET PAGINATION Slides:Pagination done the right way (by Markus Winand): The art of pagination - Offset vs. value based paging: https://www.slideshare.net/MarkusWinand/p2d2- pagination-done-the-postgresql-way http://blog.novatec-gmbh.de/art-pagination-offset-vs-value- based-paging/
  • 39.