Slide 1

Slide 1 text

Relay Deep Dive 19 April 2016 Greg Hurrell (@wincent)

Slide 2

Slide 2 text

Relay Data-management for React applications

Slide 3

Slide 3 text

Data-fetching framework for React applications

Slide 4

Slide 4 text

Data-fetching framework for React applications

Slide 5

Slide 5 text

Data-management for React applications

Slide 6

Slide 6 text

Enterprise Object Data Lifecycle Management for React applications

Slide 7

Slide 7 text

Enterprise Object Data Lifecycle-management for React applications

Slide 8

Slide 8 text

Data-management for component-based view architectures

Slide 9

Slide 9 text

What we'll cover • React <-> Relay <-> GraphQL • How Relay integrates with the view layer • Transform (Babel plug-in) • Data-fetching pipeline • Traversals • Query modeling • End-to-end flow from fetch-to-render

Slide 10

Slide 10 text

React

Slide 11

Slide 11 text

React is a JavaScript library for building user interfaces

Slide 12

Slide 12 text

Build using declarative, reusable, composable components

Slide 13

Slide 13 text

Turtles Components all the way down

Slide 14

Slide 14 text

Component hierarchies as nested objects

Slide 15

Slide 15 text

Component hierarchies as trees

Slide 16

Slide 16 text

GraphQL

Slide 17

Slide 17 text

GraphQL query StoryQuery { node(id: 1) { actor { name } createdAt updatedAt title } }

Slide 18

Slide 18 text

GraphQL query StoryQuery { node(id: 1) { actor { name } createdAt updatedAt title } }

Slide 19

Slide 19 text

GraphQL query StoryQuery { node(id: 1) { actor { name } createdAt updatedAt title } } { "node": { "actor": { "name": "Greg" }, "createdAt": "2015-10-26", "updatedAt": "2015-10-26", "title": "Relay Deep Dive" } }

Slide 20

Slide 20 text

GraphQL query StoryQuery { node(id: 1) { actor { name } createdAt updatedAt title } } { "node": { "actor": { "name": "Greg" }, "createdAt": "2015-10-26", "updatedAt": "2015-10-26", "title": "Relay Deep Dive" } }

Slide 21

Slide 21 text

React

Slide 22

Slide 22 text

React GraphQL

Slide 23

Slide 23 text

React GraphQL Relay

Slide 24

Slide 24 text

Relay colocates your data fetching queries with your components Fragment Fragment Fragment Fragment Fragment Fragment Fragment Fragment

Slide 25

Slide 25 text

Each component gets exactly (and only) the data it needs Data Data Data Data Data Data Data Data

Slide 26

Slide 26 text

Why colocate?

Slide 27

Slide 27 text

Why colocate? • Avoid overfetching (for performance)

Slide 28

Slide 28 text

Why colocate? • Avoid overfetching (for performance) • Avoid underfetching (for stability)

Slide 29

Slide 29 text

Why colocate? • Avoid overfetching (for performance) • Avoid underfetching (for stability) • Enable component-local reasoning

Slide 30

Slide 30 text

Why colocate? • Avoid overfetching (for performance) • Avoid underfetching (for stability) • Enable component-local reasoning • Reusability

Slide 31

Slide 31 text

Why colocate? • Avoid overfetching (for performance) • Avoid underfetching (for stability) • Enable component-local reasoning • Reusability • Fearless refactoring

Slide 32

Slide 32 text

Why colocate? • Avoid overfetching (for performance) • Avoid underfetching (for stability) • Enable component-local reasoning • Reusability • Fearless refactoring • Developer velocity

Slide 33

Slide 33 text

Query aggregation Fragment Fragment Fragment Fragment Fragment Fragment Fragment Fragment

Slide 34

Slide 34 text

Query aggregation Data Data Data Data Data Data Data Data Fragment Fragment Fragment Fragment Fragment Fragment Fragment Fragment

Slide 35

Slide 35 text

Query aggregation Fragment Fragment Fragment Fragment Fragment Fragment Fragment Fragment Data Data Data Data Data Data Data Data

Slide 36

Slide 36 text

Containers

Slide 37

Slide 37 text

Containers Configuration

Slide 38

Slide 38 text

Containers Configuration Logic

Slide 39

Slide 39 text

"Higher-Order" Components Story Relay.createContainer(Story, {fragments}) const WrappedComponent = wrap();

Slide 40

Slide 40 text

A fully-wrapped component tree

Slide 41

Slide 41 text

Fragments: pre-transform { fragments: { post: () => Relay.QL` fragment on Post { createdAt title ${Tags.getFragment('tagged')} } ` }, }

Slide 42

Slide 42 text

Fragments: post-transform post: function post() { return (function (RQL_0) { return { children: [{ fieldName: 'id', kind: 'Field', metadata: { isGenerated: true, isRequisite: true }, type: 'ID' }, _reactRelay2.default.QL.__frag(RQL_0)], hash: 'NrTO0r2X', kind: 'Fragment', metadata: {}, name: 'PostPreview', type: 'Post' }; })(_Tags2.default.getFragment('tagged')); }

Slide 43

Slide 43 text

The concrete AST

Slide 44

Slide 44 text

The concrete AST • Objects with simple properties: POJOs

Slide 45

Slide 45 text

The concrete AST • Objects with simple properties: POJOs • "Concrete Nodes"

Slide 46

Slide 46 text

The concrete AST • Objects with simple properties: POJOs • "Concrete Nodes" • Metadata from schema

Slide 47

Slide 47 text

Why transform? fieldName: 'id', kind: 'Field', metadata: { isGenerated: true, isRequisite: true }, type: 'ID'

Slide 48

Slide 48 text

RelayQuery.* • Immutable wrapper for concrete GraphQL nodes • Methods for cloning, traversing, querying metadata

Slide 49

Slide 49 text

Data-fetching Client RelayStore

Slide 50

Slide 50 text

Data-fetching Client RelayStore Server GraphQL

Slide 51

Slide 51 text

Data-fetching Client RelayStore Server GraphQL

Slide 52

Slide 52 text

Data-fetching Client RelayStore Server GraphQL

Slide 53

Slide 53 text

Data-fetching Client RelayStore

Slide 54

Slide 54 text

The Store Client RelayStore • A client-side cache. • Normalized, flattened graph representation.

Slide 55

Slide 55 text

Store Data query SampleQuery { node(id: 660361306) { address { country } id name hometown { id name } } }

Slide 56

Slide 56 text

Store Data query SampleQuery { node(id: 660361306) { address { country } id name hometown { id name } } } { "660361306": { "address": { "country": "US" }, "hometown": { "id": "115970731750761", "name": "Adelaide" }, "id": "660361306", "name": "Greg Hurrell", } }

Slide 57

Slide 57 text

{ 115970731750761: { __dataID__: '115970731750761', name: 'Adelaide', }, 660361306: { __dataID__: '660361306', address: { __dataID__: 'client:1', }, hometown: { __dataID__: '115970731750761', }, id: '660361306', name: 'Greg Hurrell', }, 'client:1': { __dataID__: 'client:1', country: 'US', }, } Store Data { "660361306": { "address": { "country": "US" }, "hometown": { "id": "115970731750761", "name": "Adelaide" }, "id": "660361306", "name": "Greg Hurrell", } }

Slide 58

Slide 58 text

{ 115970731750761: { __dataID__: '115970731750761', name: 'Adelaide', }, 660361306: { __dataID__: '660361306', address: { __dataID__: 'client:1', }, hometown: { __dataID__: '115970731750761', }, id: '660361306', name: 'Greg Hurrell', }, 'client:1': { __dataID__: 'client:1', country: 'US', }, } Store Data { "660361306": { "address": { "country": "US" }, "hometown": { "id": "115970731750761", "name": "Adelaide" }, "id": "660361306", "name": "Greg Hurrell", } }

Slide 59

Slide 59 text

Connections: GraphQLRange { 'client:1000': { __dataID__: 'client:1000', __filterCalls__: [], __range__: GraphQLRange(...), }, }

Slide 60

Slide 60 text

Queued and cached data RelayStore Store data Queued data Cached data

Slide 61

Slide 61 text

Queued and cached data RelayStore Store data Queued data Cached data

Slide 62

Slide 62 text

Relay.Environment.primeCache Data-fetching pipeline Diff Split deferred Subtract Print

Slide 63

Slide 63 text

Relay.Environment.primeCache Diffing Diff Split deferred Subtract Print

Slide 64

Slide 64 text

Diffing Root

Slide 65

Slide 65 text

Diffing Root Store -

Slide 66

Slide 66 text

Diffing Root Store - =

Slide 67

Slide 67 text

RelayStore.primeCache Splitting deferred queries Diff Split deferred Subtract Print

Slide 68

Slide 68 text

Deferred fragments Data we already have (from feed): essential to display permalink page. "Nice-to-have" data that we need only render "eventually".

Slide 69

Slide 69 text

Deferring a fragment fragment on Story { actors { name } body title ${StoryMenu.getFragment('story')} ${Comments.getFragment('story').defer()} }

Slide 70

Slide 70 text

Deferring a fragment fragment on Story { actors { name } body title ${StoryMenu.getFragment('story')} ${Comments.getFragment('story').defer()} }

Slide 71

Slide 71 text

Splitting deferred fragments Root

Slide 72

Slide 72 text

Splitting deferred fragments Root + = Root

Slide 73

Slide 73 text

Nested deferred fragments Root

Slide 74

Slide 74 text

Nested deferred fragments Root

Slide 75

Slide 75 text

Nested deferred fragments Root

Slide 76

Slide 76 text

Nested deferred fragments Root

Slide 77

Slide 77 text

Nested deferred fragments export type SplitQueries = { __parent__: ?SplitQueries; __path__: NodePath; __refQuery__: ?RelayRefQueryDescriptor; deferred: Array; required: ?RelayQuery.Root; };

Slide 78

Slide 78 text

Nested deferred fragments export type SplitQueries = { __parent__: ?SplitQueries; __path__: NodePath; __refQuery__: ?RelayRefQueryDescriptor; deferred: Array; required: ?RelayQuery.Root; };

Slide 79

Slide 79 text

Nested deferred fragments { required: topLevelQuery, deferred: [{ required: nestedQuery, deferred: [{ required: innermostQuery, deferred: [], }], }], }

Slide 80

Slide 80 text

Nested deferred fragments { required: topLevelQuery, deferred: [{ required: nestedQuery, deferred: [{ required: innermostQuery, deferred: [], }], }], }

Slide 81

Slide 81 text

Nested deferred fragments { required: topLevelQuery, deferred: [{ required: nestedQuery, deferred: [{ required: innermostQuery, deferred: [], }], }], }

Slide 82

Slide 82 text

Nested deferred fragments { required: topLevelQuery, deferred: [{ required: nestedQuery, deferred: [{ required: innermostQuery, deferred: [], }], }], }

Slide 83

Slide 83 text

Nested deferred fragments { required: topLevelQuery, deferred: [{ required: nestedQuery, deferred: [{ required: innermostQuery, deferred: [], }], }], }

Slide 84

Slide 84 text

Nested deferred fragments { required: topLevelQuery, deferred: [{ required: nestedQuery, deferred: [{ required: innermostQuery, deferred: [], }], }], }

Slide 85

Slide 85 text

Traversals

Slide 86

Slide 86 text

RelayQuery.* Node Root Field Fragment Operation Subscription Mutation

Slide 87

Slide 87 text

RelayQuery.* Node Root Field Fragment Operation Subscription Mutation

Slide 88

Slide 88 text

RelayQuery.* viewer fragment on User name hometown fragment on Page description ...

Slide 89

Slide 89 text

RelayQuery.* viewer fragment on User name hometown fragment on Page description ...

Slide 90

Slide 90 text

RelayQuery.* viewer fragment on User name hometown fragment on Page description ...

Slide 91

Slide 91 text

RelayQuery.* viewer fragment on User name hometown fragment on Page description ...

Slide 92

Slide 92 text

RelayQuery.* viewer fragment on User name hometown fragment on Page description ...

Slide 93

Slide 93 text

RelayQueryVisitor class RelayQueryVisitor { visit(node, nextState) {} visitField(node, nextState) {} visitFragment(node, nextState) {} visitRoot(node, nextState) {} traverse(node, nextState) {} }

Slide 94

Slide 94 text

RelayQueryVisitor class RelayQueryVisitor { visit(node, nextState) {} visitField(node, nextState) {} visitFragment(node, nextState) {} visitRoot(node, nextState) {} traverse(node, nextState) {} }

Slide 95

Slide 95 text

RelayQueryVisitor class RelayQueryVisitor { visit(node, nextState) {} visitField(node, nextState) {} visitFragment(node, nextState) {} visitRoot(node, nextState) {} traverse(node, nextState) {} }

Slide 96

Slide 96 text

RelayQueryVisitor class RelayQueryVisitor { visit(node, nextState) {} visitField(node, nextState) {} visitFragment(node, nextState) {} visitRoot(node, nextState) {} traverse(node, nextState) {} }

Slide 97

Slide 97 text

RelayQueryVisitor class RelayQueryVisitor { visit(node, nextState) {} visitField(node, nextState) {} visitFragment(node, nextState) {} visitRoot(node, nextState) {} traverse(node, nextState) {} }

Slide 98

Slide 98 text

RelayQueryVisitor class RelayQueryVisitor { visit(node, nextState) {} visitField(node, nextState) {} visitFragment(node, nextState) {} visitRoot(node, nextState) {} traverse(node, nextState) {} }

Slide 99

Slide 99 text

RelayQueryTransform class RelayQueryTransform extends RelayQueryVisitor { traverse(node, nextState) {} }

Slide 100

Slide 100 text

RelayQueryTransform class RelayQueryTransform extends RelayQueryVisitor { traverse(node, nextState) {} }

Slide 101

Slide 101 text

splitDeferredRelayQueries visitFragment(node, splitQueries) { if (node.isDeferred()) { const childSplitQueries = {}; const required = this.traverse(node, childSplitQueries); if (required) { const wrapped = wrapNode(required); childSplitQueries.required = wrapped; } if (required || childSplitQueries.deferred.length) { splitQueries.deferred.push(childSplitQueries); } return null; } else if (node.hasDeferredDescendant()) { return this.traverse(node, splitQueries); } else { return node; } }

Slide 102

Slide 102 text

splitDeferredRelayQueries visitFragment(node, splitQueries) { if (node.isDeferred()) { const childSplitQueries = {}; const required = this.traverse(node, childSplitQueries); if (required) { const wrapped = wrapNode(required); childSplitQueries.required = wrapped; } if (required || childSplitQueries.deferred.length) { splitQueries.deferred.push(childSplitQueries); } return null; } else if (node.hasDeferredDescendant()) { return this.traverse(node, splitQueries); } else { return node; } }

Slide 103

Slide 103 text

splitDeferredRelayQueries visitFragment(node, splitQueries) { if (node.isDeferred()) { const childSplitQueries = {}; const required = this.traverse(node, childSplitQueries); if (required) { const wrapped = wrapNode(required); childSplitQueries.required = wrapped; } if (required || childSplitQueries.deferred.length) { splitQueries.deferred.push(childSplitQueries); } return null; } else if (node.hasDeferredDescendant()) { return this.traverse(node, splitQueries); } else { return node; } }

Slide 104

Slide 104 text

splitDeferredRelayQueries visitFragment(node, splitQueries) { if (node.isDeferred()) { const childSplitQueries = {}; const required = this.traverse(node, childSplitQueries); if (required) { const wrapped = wrapNode(required); childSplitQueries.required = wrapped; } if (required || childSplitQueries.deferred.length) { splitQueries.deferred.push(childSplitQueries); } return null; } else if (node.hasDeferredDescendant()) { return this.traverse(node, splitQueries); } else { return node; } }

Slide 105

Slide 105 text

splitDeferredRelayQueries visitFragment(node, splitQueries) { if (node.isDeferred()) { const childSplitQueries = {}; const required = this.traverse(node, childSplitQueries); if (required) { const wrapped = wrapNode(required); childSplitQueries.required = wrapped; } if (required || childSplitQueries.deferred.length) { splitQueries.deferred.push(childSplitQueries); } return null; } else if (node.hasDeferredDescendant()) { return this.traverse(node, splitQueries); } else { return node; } }

Slide 106

Slide 106 text

splitDeferredRelayQueries visitFragment(node, splitQueries) { if (node.isDeferred()) { const childSplitQueries = {}; const required = this.traverse(node, childSplitQueries); if (required) { const wrapped = wrapNode(required); childSplitQueries.required = wrapped; } if (required || childSplitQueries.deferred.length) { splitQueries.deferred.push(childSplitQueries); } return null; } else if (node.hasDeferredDescendant()) { return this.traverse(node, splitQueries); } else { return node; } }

Slide 107

Slide 107 text

splitDeferredRelayQueries visitFragment(node, splitQueries) { if (node.isDeferred()) { const childSplitQueries = {}; const required = this.traverse(node, childSplitQueries); if (required) { const wrapped = wrapNode(required); childSplitQueries.required = wrapped; } if (required || childSplitQueries.deferred.length) { splitQueries.deferred.push(childSplitQueries); } return null; } else if (node.hasDeferredDescendant()) { return this.traverse(node, splitQueries); } else { return node; } }

Slide 108

Slide 108 text

splitDeferredRelayQueries visitFragment(node, splitQueries) { if (node.isDeferred()) { const childSplitQueries = {}; const required = this.traverse(node, childSplitQueries); if (required) { const wrapped = wrapNode(required); childSplitQueries.required = wrapped; } if (required || childSplitQueries.deferred.length) { splitQueries.deferred.push(childSplitQueries); } return null; } else if (node.hasDeferredDescendant()) { return this.traverse(node, splitQueries); } else { return node; } }

Slide 109

Slide 109 text

splitDeferredRelayQueries visitFragment(node, splitQueries) { if (node.isDeferred()) { const childSplitQueries = {}; const required = this.traverse(node, childSplitQueries); if (required) { const wrapped = wrapNode(required); childSplitQueries.required = wrapped; } if (required || childSplitQueries.deferred.length) { splitQueries.deferred.push(childSplitQueries); } return null; } else if (node.hasDeferredDescendant()) { return this.traverse(node, splitQueries); } else { return node; } }

Slide 110

Slide 110 text

RelayStore.primeCache Query subtraction Diff Split deferred Subtract Print

Slide 111

Slide 111 text

Query subtraction New query

Slide 112

Slide 112 text

Query subtraction New query - In-flight queries

Slide 113

Slide 113 text

Query subtraction New query - In-flight queries = Final query

Slide 114

Slide 114 text

RelayStore.primeCache Query printing Diff Split deferred Subtract Print

Slide 115

Slide 115 text

Query printing AST query ViewerQuery { viewer { actor { name } isEmployee timezone zodiac } }

Slide 116

Slide 116 text

RelayStore.primeCache Data-fetching pipeline Diff Split deferred Subtract Print

Slide 117

Slide 117 text

Data-fetching pipeline

Slide 118

Slide 118 text

Data-fetching pipeline Server

Slide 119

Slide 119 text

Data-fetching pipeline Server

Slide 120

Slide 120 text

Data-fetching pipeline Server

Slide 121

Slide 121 text

Data-fetching pipeline Server Write payload

Slide 122

Slide 122 text

Data-fetching pipeline Server Write payload Notify subscribers

Slide 123

Slide 123 text

Data-fetching pipeline Server Write payload Notify subscribers Read query data

Slide 124

Slide 124 text

Data-fetching pipeline Server Write payload Notify subscribers Read query data Render

Slide 125

Slide 125 text

visitFragment(node, state) { if (node.isContainerFragment()) { const dataID = getComponentDataID(state); const fragmentPointer = new GraphQLFragmentPointer( dataID, node ); this._setDataValue(state, fragmentPointer); } else { this.traverse(node, state); } } Reading query data

Slide 126

Slide 126 text

visitFragment(node, state) { if (node.isContainerFragment()) { const dataID = getComponentDataID(state); const fragmentPointer = new GraphQLFragmentPointer( dataID, node ); this._setDataValue(state, fragmentPointer); } else { this.traverse(node, state); } } Reading query data

Slide 127

Slide 127 text

Reading query data visitFragment(node, state) { if (node.isContainerFragment()) { const dataID = getComponentDataID(state); const fragmentPointer = new GraphQLFragmentPointer( dataID, node ); this._setDataValue(state, fragmentPointer); } else { this.traverse(node, state); } }

Slide 128

Slide 128 text

Reading query data visitFragment(node, state) { if (node.isContainerFragment()) { const dataID = getComponentDataID(state); const fragmentPointer = new GraphQLFragmentPointer( dataID, node ); this._setDataValue(state, fragmentPointer); } else { this.traverse(node, state); } }

Slide 129

Slide 129 text

Reading query data visitFragment(node, state) { if (node.isContainerFragment()) { const dataID = getComponentDataID(state); const fragmentPointer = new GraphQLFragmentPointer( dataID, node ); this._setDataValue(state, fragmentPointer); } else { this.traverse(node, state); } }

Slide 130

Slide 130 text

Trees

Slide 131

Slide 131 text

Other topics • Garbage collection • Mutations • Tracked queries & "Fat" queries • GraphQL Subscriptions • Routing • Server rendering • And many others...