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.