Slide 1

Slide 1 text

An introduction to Graphene and Relay

Slide 2

Slide 2 text

Part 0: Introduction

Slide 3

Slide 3 text

Who am I? • Marc Tamlyn • Django Core Developer • Graphene contributor • Occasional Javascript developer • Work at Photocrowd

Slide 4

Slide 4 text

Workshop structure • Split into four main parts • GraphQL • Graphene • React • Relay

Slide 5

Slide 5 text

What is all this? • GraphQL is a new API structure created by Facebook • Graphene is a declarative python library for creating GraphQL APIs by the python-graphql organisation • It builds on top of graphql-core, a python version of the "official" graphql-js library • React is a declarative, component based Javascript rendering engine created by Facebook • Relay is a React-drive framework for consuming a GraphQL API and building a highly flexible and reusable component based application architecture

Slide 6

Slide 6 text

What's the point? • Allow your front ends to declare the shape of the data they require • Move n+1 problems to the API server instead of the front end • Components declare their own requirements and can be composed, allowing changes deep in the system to work everywhere • Great for applications where the same objects are represented and connected in many different ways

Slide 7

Slide 7 text

A word on investment • Personally, I find these concepts are relatively "hard" compared to understanding a REST API • It takes a higher investment of effort to understand all the pieces and how they all fit together than some other architectures • It requires more work and rigour to code using these libraries • Personally, I think the effort pays off • Give it a week!

Slide 8

Slide 8 text

Data setup • A little (fictional!) welsh village • 3 streets, 10 houses • People live in the houses and are related to each other • People can get married, have children and move house • Application should allow you to explore the village and find out about the people who live there, and update the data as they move, marry, have children and so on

Slide 9

Slide 9 text

Part 1: GraphQL

Slide 10

Slide 10 text

GraphQL • New query language • Strongly typed • Returns JSON

Slide 11

Slide 11 text

Work along with me https://graphene-tutorial.herokuapp.com/plain/ https://github.com/mjtamlyn/graphene-tutorial/

Slide 12

Slide 12 text

Type system • Everything consists of fields on an object • Useful to know what fields are available, and what type of data to expect • Allows static validation of queries

Slide 13

Slide 13 text

Scalars • Int • Float • String • Boolean • ID • Can define your own - needs to be JSON serialisable

Slide 14

Slide 14 text

Lists and nulls • List of any other type • Fields are nullable unless specified as non-null

Slide 15

Slide 15 text

Enumerations types • First class support for enums • Collection of unique string names • Allows query-time validation of arguments

Slide 16

Slide 16 text

Type system • Object type • Consists of fields • Each field returns data of a specified type • Data can be any type, including other object types • Each field may take arguments • Each argument also has a fixed type, and may or may not be nullable

Slide 17

Slide 17 text

Interfaces and Unions • Object types can be combined in a union • They can implement one or more interfaces, keeping a common set of behaviour between multiple types

Slide 18

Slide 18 text

Schema • Schema is an ObjectType • Must have a query field • May have a mutation field

Slide 19

Slide 19 text

Queries • Written in GraphQL query language • Returns JSON

Slide 20

Slide 20 text

A simple query query { streets { name } } {"data": { "streets": [ {"name": "Afon Stryd"}, {"name": "Glyn Stryd"}, {"name": "Ysgol Stryd"} ] }}

Slide 21

Slide 21 text

Nesting query { streets { name houses { number } } } {"data": { "streets": [ {"name": "Afon Stryd", "houses": [ {"number": 1}, {"number": 2}, {"number": 3}, ]}, …

Slide 22

Slide 22 text

Multiple queries query { streets { name } houses { name } } {"data": { "streets": [ {"name": "Afon Stryd" … ] "houses": [ {"name": "1 Afon Stryd"} …

Slide 23

Slide 23 text

Arguments and variables query { people { name family relationships(relationship: child) { name family } } }

Slide 24

Slide 24 text

Arguments and variables query People($relationship: Relationship!) { people { name family relationships(relationship: $relationship) { name family } } } variables {"relationship": "child"}

Slide 25

Slide 25 text

Arguments and variables {"data": { "people": [ {"name": "Lowri", "family": "Jones", "relationships": [ {"name": "Dafydd", "family": "Jones"}, {"name": "Bethan", "family": "Jones"}] …

Slide 26

Slide 26 text

Fragments and aliases fragment F0 on Person { name residence { name } }

Slide 27

Slide 27 text

Fragments and aliases query { people { name family children: relationships(relationship: child) {...F0} parents: relationships(relationship: parent) {...F0} } }

Slide 28

Slide 28 text

Fragments and aliases "people": [ {"name": "Lowri", "family": "Jones", "children": [ {"name": "Dafydd", "residence": { "name": "1 Glyn Stryd"} }, "parents": []}, …

Slide 29

Slide 29 text

Inline fragments { search(q: "ysgol") { __typename ... on Street { name } ... on House { street { name } number } } }

Slide 30

Slide 30 text

Inline fragments "search": [ {"__typename": "House", "street": { "name": "Ysgol Stryd" }, "number": 1 }, {"__typename": "Street", "name": "Ysgol Stryd" }, … ]

Slide 31

Slide 31 text

Directives query People($includeChildren: Boolean!) { people { name family relationships(relationship: child) @include (if: $includeChildren) { name } } } variables {"includeChildren": false}

Slide 32

Slide 32 text

Directives { "data": { "people": [ { "name": "Lowri", "family": "Jones" }, … }

Slide 33

Slide 33 text

Have an explore! https://graphene-tutorial.herokuapp.com/plain/

Slide 34

Slide 34 text

Mutations • The only way to change data with a GraphQL API • Looks like a field - takes arguments and returns an ObjectType • Run in series instead of in parallel

Slide 35

Slide 35 text

Input type • Can define a special kind of type called and input type • Allows you to send the JSON representation of that type as a variable value

Slide 36

Slide 36 text

Making a mutation mutation Move($input: MoveInput!) { move(input: $input) { name residence { name } } } variables {"input": {"person": "ID1", "residence": "ID2"}}

Slide 37

Slide 37 text

Part 2: Graphene

Slide 38

Slide 38 text

Defining an object type • Let's start with something really simple • Street has just one attribute - its name • It should be a string

Slide 39

Slide 39 text

Defining an object type class Street(graphene.ObjectType): name = graphene.Field(graphene.String) And you're done!

Slide 40

Slide 40 text

Defining an object type - shorthand class Street(graphene.ObjectType): name = graphene.String()

Slide 41

Slide 41 text

Object types as containers street = Street(name='Araf Stryd')

Slide 42

Slide 42 text

A note on naming • GraphQL expects to have camelCase names • Python doesn't like camelCase names • Graphene just translates them for you - so an object type field named house_pets becomes a GraphQL field named housePets

Slide 43

Slide 43 text

Custom initialisation and resolvers • You will likely want to translate from your application objects to ObjectTypes • Some fields you want to expose you don't know yet • Custom resolver method using the resolve_FOO() pattern • Resolvers receive the field arguments, your own context and the current resolver info

Slide 44

Slide 44 text

Custom initialisation and resolvers class Street(graphene.ObjectType): name = graphene.String() def __init__(self, street): self.street = street def resolve_name(self, args, ctx, info): return self.street.name

Slide 45

Slide 45 text

Custom initialisation and resolvers class Street(graphene.ObjectType): houses = graphene.List(House) def resolve_houses(self, args, context, info): return [ House(h) for h in population.houses if h.street == self.street ]

Slide 46

Slide 46 text

Custom fields • Fields have their own resolver method • Allows you to share common custom resolving logic between multiple object types

Slide 47

Slide 47 text

Defining an enum • Declarative • Uses python 3's enum.Enum (or a backport)

Slide 48

Slide 48 text

Defining an enum class Relationship(graphene.Enum): parent = 'parent' grandparent = 'grandparent' child = 'child' grandchild = 'grandchild' spouse = 'spouse' sibling = 'sibling'

Slide 49

Slide 49 text

Defining a field with arguments • Allows fields to behave differently depending on what the client needs • Examples would include pagination, image sizes, filtering a dataset

Slide 50

Slide 50 text

Defining a field with arguments class Person(graphene.ObjectType): relationships = graphene.Field( graphene.List(lambda: Person), relationship=graphene.Argument( Relationship, required=True, ), )

Slide 51

Slide 51 text

Defining a field with arguments class Person(graphene.ObjectType): relationships = graphene.List( lambda: Person, relationship=graphene.Argument( Relationship, required=True, ), )

Slide 52

Slide 52 text

Defining a field with arguments class Person(graphene.ObjectType): def resolve_relationships( self, args, context, info ): if args['relationship'] == 'parent': return [ Person(p) for p in self.person.parents ] …

Slide 53

Slide 53 text

Union Type • Allows returning one of a variety of types • Example use cases include search endpoints, generic relationships, mixed content feeds • Resolver returns any of the valid objects and graphene does the rest

Slide 54

Slide 54 text

Union Type class Anything(graphene.types.union.Union): class Meta: types = (Person, House, Street)

Slide 55

Slide 55 text

Creating your schema • Create a Query ObjectType • Plumb that into the schema

Slide 56

Slide 56 text

Creating your schema class Query(graphene.ObjectType): people = graphene.List(Person) houses = graphene.List(House) streets = graphene.List(Street) search = graphene.Field( graphene.List(Anything), q=graphene.String(required=True) ) )

Slide 57

Slide 57 text

Creating your schema schema = graphene.Schema(query=Query) schema_with_mutations = graphene.Schema( query=Query, mutation=Mutation, )

Slide 58

Slide 58 text

Using the schema directly >>> schema.execute(''' query { streets { name } } ''') {"data": {"streets": […]}}

Slide 59

Slide 59 text

Context and info • When you use the schema, you can provide context • This is useful for attaching authentication or authorisation information you wish to use in resolvers • Info provides information about the current set of fields "below" where you are which are requested • Advanced use case, but can use to optimise the way you load data for the children

Slide 60

Slide 60 text

Mutations • Must define its Input type • Normal class definition is the object type returned • Has a mutate classmethod which does the work if the input is valid

Slide 61

Slide 61 text

Mutations class MoveHouse(graphene.Mutation): class Input: person = graphene.Field(Person) house = graphene.Field(House) # output person = graphene.Field(Person) ok = graphene.Boolean()

Slide 62

Slide 62 text

Mutations class MoveHouse(graphene.Mutation): @classmethod def mutate(cls, instance, args, info): person = get_person(args['person']) house = get_house(args['house']) ok = person.move_to(house) return MoveHouse(person=person, ok=ok)

Slide 63

Slide 63 text

Deploying with Django • Use the graphene-django package • The django-graphiql package allows interactive testing - add it to INSTALLED_APPS • Integration tools exist for other systems like SQLAlchemy

Slide 64

Slide 64 text

Deploying with Django from django.conf.urls import url from django.views.decorators.csrf import csrf_exempt from graphene_django.views import GraphQLView from django_graphiql.views import GraphiQL from .api import Schema urlpatterns = [ url(r'^graphql', csrf_exempt( GraphQLView.as_view(schema=schema))), url(r'^graphiql', GraphiQL.as_view()), ]

Slide 65

Slide 65 text

Part 3: React

Slide 66

Slide 66 text

What is React? • View layer of a traditional MVC • Virtual DOM • Single directional data flow

Slide 67

Slide 67 text

What is a component? • Takes input (props) • Returns an element • Class with a render() method

Slide 68

Slide 68 text

What is a component? var houses = React.createClass({ render: function() { var photos = this.props.houses.map((house) => { return React.createElement(House, { number: house.number, streetName: house.street.name, }); }); return React.createElement('div', {}, houses); } });

Slide 69

Slide 69 text

JSX • Preprocessor for React.createElement() • Compile using your JS preprocessor of choice

Slide 70

Slide 70 text

JSX var houses = React.createClass({ render: function() { var photos = this.props.houses.map((house) => { return ; }); return
{ houses }
; } });

Slide 71

Slide 71 text

Virtual DOM • Renders in memory-light virtual DOM • Compares changes between trees • Flushes only changes to the document • Debounces rendering

Slide 72

Slide 72 text

Data flow • Components have state as well as props • Minimise use of state • Handle at top level and pass functions around • Flux-like architectures • Reflux • Redux • Use Relay!

Slide 73

Slide 73 text

Part 4: Relay

Slide 74

Slide 74 text

Why Relay? • Only GraphQL-focused React framework • Colocation • Data masking • Caching • Query optimisation • Loading and ready states • Don't want something this restrictive? Try apollo-client

Slide 75

Slide 75 text

A Relay-compliant GraphQL API • Relay imposes conventions on the shape of your API • Caches and optimises queries through nodes • Handles pagination intelligently via cursors and connections

Slide 76

Slide 76 text

Nodes • All "core" objects should be implemented as Nodes • If there would be a "detail" page of it, it should be a node • All nodes have a globally unique ID • Query has a node field which can query any node

Slide 77

Slide 77 text

Nodes class StreetNode(DjangoObjectType): class Meta: model = Street only_fields = ['name'] interfaces = (relay.Node,)

Slide 78

Slide 78 text

Nodes query { streets { id name } } {"data": { "streets": [ {"id": "XXXXXX", "name": "Afon Stryd"}, {"id": "YYYYYY",
 "name": "Glyn Stryd"}, {"id": "ZZZZZZ",
 "name": "Ysgol Stryd"} ] }}

Slide 79

Slide 79 text

Nodes query { node(id: "XXXXXX") { id name } } {"data": { "node": { "id": "XXXXXX", "name": "Afon Stryd", } }}

Slide 80

Slide 80 text

Connections • Connections are special object types which have a particular structure • Used to do cursor based pagination • first - after • last - before • Return edges and pageInfo • Edges have cursors and nodes

Slide 81

Slide 81 text

Connections class StreetNode(DjangoObjectType): houses = relay.ConnectionField( HouseNode) def resolve_houses(self, args, ctx, info): return self.house_set.all()

Slide 82

Slide 82 text

Connections query { streets { name houses(first: 1) { edges { cursor node { number } } } } }

Slide 83

Slide 83 text

Connections {"data": {"streets" { "name": "Afon Stryd", "houses": { "edges": [{ "cursor": "AAAAAA", "node": { "number": 1, } }, …

Slide 84

Slide 84 text

Connections query { streets { name houses(first: 1) { pageInfo { hasNextPage endCursor } } } }

Slide 85

Slide 85 text

Connections {"data": {"streets" { "name": "Afon Stryd", "houses": { "pageInfo": { "hasNextPage": true, "endCursor": "AAAAAA", } …

Slide 86

Slide 86 text

Connections query { streets { name houses(first: 1, after: "AAAAAA") { … } } }

Slide 87

Slide 87 text

Mutations • Mutations must both take and return a unique identifier • Allows multiple similar mutations to be checked individually by the framework

Slide 88

Slide 88 text

Relay containers • A higher order component • Function which takes a component and returns a component which wraps it • Compile-time declares the data shape that the component needs

Slide 89

Slide 89 text

Declaring fragments • Uses Relay.QL`` • A little sugar on top of a standard GraphQL fragment to allow reference to variables more easily • Looks a bit like ES6 template literals • Compiles using a babel plugin

Slide 90

Slide 90 text

Composing containers • When building the fragments, include fragments from the child • Match the name of the prop passed to the child to the fragment that child declared

Slide 91

Slide 91 text

Root Containers • Declares the entry point to the Relay application • Consists of a component to render, and a "rooted" query to the Query on the Node • Don't try to be too clever with them, they have some strange restrictions about arguments which will likely be removed in future versions

Slide 92

Slide 92 text

The "viewer" pattern • Works around Relay Root queries being weird by doing everything from a viewer field on Query • Don't forget that Relay's node field still needs to be on Query as well • Idea is everything is localised to what the user can see

Slide 93

Slide 93 text

Mutations • Must receive and return the client identifier • Just use relay.ClientIDMutation

Slide 94

Slide 94 text

Babel and webpack • Use babel to translate JSX and Relay.QL • Webpack with Django to create "bundles" of javascript to render to the page • graphene-django creates the schema file needed to verify the Relay.QL

Slide 95

Slide 95 text

Dumping your schema $ python manage.py graphql_schema --schema relay.api { "data":{ "__schema":{ "queryType":{ "name":"Query" }, "mutationType":null, "subscriptionType":null, "types":[ { "kind":"OBJECT", "name":"Query", "description":null, "fields":[ { "name":"streets", "description":null, "args":[ ], …

Slide 96

Slide 96 text

Things to install • Babel plugins required • babel-preset-es2015 • babel-preset-react • babel-relay-plugin • babel-relay-plugin-loader • Django • django-webpack-loader • python manage.py graphql_schema --schema --out schema.json

Slide 97

Slide 97 text

Webpack config var BundleTracker = require('webpack-bundle- tracker') plugins: [ new BundleTracker({ filename: './build/webpack-stats.json' }), ],

Slide 98

Slide 98 text

Webpack config module: { loaders: [ { test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel-loader', } ] }

Slide 99

Slide 99 text

package.json config "metadata": { "graphql": { "schema": "./schema.json" } }

Slide 100

Slide 100 text

Conclusion

Slide 101

Slide 101 text

Conclusion • Stack of tools for building complete applications • A coherent and powerful API • Hurdle to learn but expressive and effective once learned • Excellent support for complex applications • Probably overkill for simple ones • Excellent responsiveness to changing visual and interactivity requirements