O slideshow foi denunciado.
Utilizamos seu perfil e dados de atividades no LinkedIn para personalizar e exibir anúncios mais relevantes. Altere suas preferências de anúncios quando desejar.
Moving to GraphQL
Scott Taylor, The New York Times
GraphQL Summit 2017
San Francisco, CA
The (failing) New
York Times
Scott Taylor
• I live in Brooklyn, NY with my wife, Allie (+ cats)
• Sr. Software Engineer, Web Frameworks

The New York T...
Replatform
Goals
• Simplify our stack

• One place to add and retrieve data 

• Common language / repository for creating / reusing
c...
Our Last
Replatform
Our Last Replatform
• NYT5 - 4 short years ago
• “We are never taking this long on a project
ever again” - our CEO at the ...
So WHY replatform?
• Consistency: Design changes should affect
as few codebases as possible
• Desktop and mobile apps will...
Technical Notes
• moving Datacenter apps to Google Cloud
Platform

• NYTimes/drone-gke

NYTimes/drone-gae

• Moving data p...
GraphQL at the NYT
• the GraphQL server is a Sangria app, written
in Scala, maintained by a data team called
Samizdat

• t...
React/GraphQL at
the NYT. Why?
Schema Evolution
Do Not Mirror Legacy Data
(we mirrored legacy data)
Versioning
(we versioned the schema)
Pivots
• initially mirrored REST schema

• versioned the schema / generated from ProtoBufs

• versioned the schema again, ...
Relay
• v0.1.0 through 0.7.3 were all released (on GitHub) 

on March 4, 2016

• v0.8.0 was April 2016

• v0.9.0 was May 2016

•...
Relay Modern
May 19, 2017
The Road Forward
• Relay Modern

• Apollo
Ecosystem
Relay Classic
• React Router v3

• React Router Relay

• Isomorphic Relay Router

• custom Network Layer
<Route	component={App}>	
		<Route	path="/:id"	component={Story}	queries={{	
				story:	()	=>	Relay.QL`query	{	article(id:	...
<Route	component={App}>	
		<Route	path="/:id"	component={Story}	queries={{	
				story:	()	=>	Relay.QL`query	{	article(id:	...
<Route	component={App}>	
		<Route	path="/:id"	component={Story}	queries={{	
				story:	()	=>	Relay.QL`query	{	article(id:	...
<Route	component={App}>	
		<Route	path="/:id"	component={Story}	queries={{	
				story:	()	=>	Relay.QL`query	{	article(id:	...
export	default	Relay.createContainer(CardMedia,	{	
		initialVariables:	{	
				crop:	'jumbo',	
		},	
		fragments:	{	
				me...
export	default	Relay.createContainer(Image,	{	
		initialVariables:	{	
				crop:	'jumbo',	
		},	
		fragments:	{	
				media:...
this.props.relay.setVariables({	isClient:	true	});	
fragment	Component_data	on	Data	{	
		data	
		userData	@include(if:	$is...
export	default	class	RemoveFromReadingListMutation	extends	Relay.Mutation	{	
		getMutation	=	()	=>	Relay.QL`mutation	{	rem...
import	{	match	}	from	'react-router';	
import	IsomorphicRouter	from	'isomorphic-relay-router';	
import	NYTNetworkLayer	fro...
Relay Modern
• Found Relay - 0.3.0-alpha.9

• <QueryRenderer	/> is not
isomorphic

• custom fetch( ) +
QueryResponseCache
export	const	routeConfig	=	makeRouteConfig(	
		<Route	
				path="/"	
				Component={App}	
				query={AppQuery}>	
				<Rout...
const	source	=	new	RecordSource();	
const	store	=	new	Store(source);	
const	network	=	Network.create(fetchQuery);	
const	e...
export	default	createFragmentContainer(CardMedia,	{	
		media:	graphql`	
				fragment	CardMedia_media	on	Media	{	
...Image_...
import	{	graphql	}	from	'react-relay';	
export	default	graphql`	
		query	Story_Query($id:	ID!,	$crop:	[CropName!]!)	{	
			...
createRefetchContainer(Component,	graphql`…`);	
this.props.relay.refetch({	searchTerm:	'tacos'	});
export	default	createPaginationContainer(	
		Term,	
		graphql`	
				fragment	Term_viewer	on	Viewer	{	
						term(slug:	$sl...
this.props.relay.loadMore(10)
const	updater	=	store	=>	{	
		const	payload	=	store.getRootField('addComment');	
		const	newComment	=	payload.getLinkedRec...
so now what?
Apollo
Apollo
• use any Router you want

• no need to customize network

• SSR/Store rehydration requires
very little setup
export	default	(	
		<Route	path="/"	component={App}>	
				<Route	path="/:id"	component={Story}	/>	
				<Route	component={H...
const	client	=	new	ApolloClient({	
						ssrMode:	true,	
						networkInterface:	networkInterface(uri),	
						fragmentMat...
const	client	=	new	ApolloClient({	
						initialState:	window.__APOLLO_STATE__,	
						networkInterface:	networkInterface(...
@graphql(HomeQuery,	{	
		options:	{	
				variables:	{	
						crop:	'jumbo',	
				},	
		},	
})	
export	default	class	Home	e...
@graphql(HomeQuery,	{	
		options:	{	
				ssr:	false,	
		},	
})	
export	default	class	Home	extends	Component	{	
...	
}
Apollo Options
• import	{	gql	}	from	'react-apollo'	
• import	gql	from	'graphql-tag'	
• .graphql files 

• babel-plugin-inl...
export	default	function	Media()	{	
		.	.	.	
}	
Media.fragments	=	{	
		node:	gql`	
				fragment	Media_node	on	Media	{	
				...
export	default	function	Media()	{	
		.	.	.	
}	
Media.fragments	=	{	
		node:	gql`	
				fragment	Media_node	on	Media	{	
				...
#import	"./Archive_posts.graphql"	
query	Term_Query(	
		$slug:	String!,		
		$cursor:	String,		
		$count:	Int	
)	{	
		viewe...
this.props.data.refetch({	…	})	
this.props.data.fetchMore({	…	})
Isomorphic
React/GraphQL
The Problem with
HTML in React
and GraphQL
Structured Data
• Union types: BlockUnion and InlineUnion

• fragments and queries can become enormous when
using heteroge...
Scripts in HTML
• JavaScript will not execute when simply:



<div	dangerouslySetInnerHTML={{	__html:	content	}}	/>

• Scr...
Roadmap
• Moving to React 16, adjusting to the ecosystem

• Continue following developments with Relay/Apollo

• Continue ...
We're Hiring!
•All jobs: 

https://nytimes.wd5.myworkdayjobs.com/NYT/
•Home team: 

https://nytimes.wd5.myworkdayjobs.com/...
Thank You.
The New York Times: Moving to GraphQL
The New York Times: Moving to GraphQL
The New York Times: Moving to GraphQL
The New York Times: Moving to GraphQL
The New York Times: Moving to GraphQL
The New York Times: Moving to GraphQL
The New York Times: Moving to GraphQL
The New York Times: Moving to GraphQL
The New York Times: Moving to GraphQL
Próximos SlideShares
Carregando em…5
×

The New York Times: Moving to GraphQL

596 visualizações

Publicada em

React and GraphQL: Under the Hood of The Times Website Redesign https://open.nytimes.com/react-relay-and-graphql-under-the-hood-of-the-times-website-redesign-22fb62ea9764

Publicada em: Internet
  • Seja o primeiro a comentar

The New York Times: Moving to GraphQL

  1. 1. Moving to GraphQL Scott Taylor, The New York Times GraphQL Summit 2017 San Francisco, CA
  2. 2. The (failing) New York Times
  3. 3. Scott Taylor • I live in Brooklyn, NY with my wife, Allie (+ cats) • Sr. Software Engineer, Web Frameworks
 The New York Times • WordPress core team • Musician • @wonderboymusic on Twitter • @staylor on GitHub
  4. 4. Replatform
  5. 5. Goals • Simplify our stack • One place to add and retrieve data  • Common language / repository for creating / reusing components • Make onboarding easier
  6. 6. Our Last Replatform
  7. 7. Our Last Replatform • NYT5 - 4 short years ago • “We are never taking this long on a project ever again” - our CEO at the champagne toast celebrating the above. • (We are taking this long on the project currently)
  8. 8. So WHY replatform? • Consistency: Design changes should affect as few codebases as possible • Desktop and mobile apps will be one • iPhone and iPad rewritten as one in Swift • Android and iOS render WebViews
  9. 9. Technical Notes • moving Datacenter apps to Google Cloud Platform • NYTimes/drone-gke
 NYTimes/drone-gae • Moving data pipeline to internal schema that uses ProtoBufs / writes to Kafka • KYT: manages configuration of Node
  10. 10. GraphQL at the NYT • the GraphQL server is a Sangria app, written in Scala, maintained by a data team called Samizdat • the frontend is a universal Relay (Classic) app, running on Node/Express, maintained by the Web Frameworks team
  11. 11. React/GraphQL at the NYT. Why?
  12. 12. Schema Evolution
  13. 13. Do Not Mirror Legacy Data (we mirrored legacy data)
  14. 14. Versioning (we versioned the schema)
  15. 15. Pivots • initially mirrored REST schema • versioned the schema / generated from ProtoBufs • versioned the schema again, driven by product • after going all in on designing schema using NYT design terminology, decided to remove some abstractions to avoid fragment complexity
  16. 16. Relay
  17. 17. • v0.1.0 through 0.7.3 were all released (on GitHub) 
 on March 4, 2016 • v0.8.0 was April 2016 • v0.9.0 was May 2016 • v0.9.1 was June 2016 • v0.9.2 was July 2016 • v0.9.3 was September 2016 • v0.10.0 was December 2016
  18. 18. Relay Modern May 19, 2017
  19. 19. The Road Forward • Relay Modern • Apollo
  20. 20. Ecosystem
  21. 21. Relay Classic • React Router v3 • React Router Relay • Isomorphic Relay Router • custom Network Layer
  22. 22. <Route component={App}> <Route path="/:id" component={Story} queries={{ story: () => Relay.QL`query { article(id: $id) }`, }} /> <Route path="/" component={Home} queries={{ home: () => Relay.QL`query { home }`, }} /> </Route>
  23. 23. <Route component={App}> <Route path="/:id" component={Story} queries={{ story: () => Relay.QL`query { article(id: $id) }`, }} /> <Route path="/" component={Home} queries={{ home: () => Relay.QL`query { home }`, }} /> </Route>
  24. 24. <Route component={App}> <Route path="/:id" component={Story} queries={{ story: () => Relay.QL`query { article(id: $id) }`, }} /> <Route path="/" component={Home} queries={{ home: () => Relay.QL`query { home }`, }} /> </Route>
  25. 25. <Route component={App}> <Route path="/:id" component={Story} queries={{ story: () => Relay.QL`query { article(id: $id) }`, }} /> <Route path="/" component={Home} queries={{ home: () => Relay.QL`query { home }`, }} /> </Route>
  26. 26. export default Relay.createContainer(CardMedia, { initialVariables: { crop: 'jumbo', }, fragments: { media: ({ crop }) => Relay.QL` fragment CardMedia_media on Media { ${Image.getFragment('media', { crop })} } `, }, });
  27. 27. export default Relay.createContainer(Image, { initialVariables: { crop: 'jumbo', }, fragments: { media: () => Relay.QL` fragment Image_media on Image { crops(renditionNames: $crop) { renditions { name url width height } } } `, }, });
  28. 28. this.props.relay.setVariables({ isClient: true }); fragment Component_data on Data { data userData @include(if: $isClient) } this.props.relay.setVariables({ count: count + 10 });
  29. 29. export default class RemoveFromReadingListMutation extends Relay.Mutation { getMutation = () => Relay.QL`mutation { removeFromReadingList }`; getVariables = () => ({ clientMutationId: this.props.id, url: this.props.url, userIdentifier: { token }, }); getFatQuery = () => Relay.QL` fragment on ReadingListMutationResult { readingListAssetsConnection(first: 200) { edges } } `; getConfigs = () => [{ type: 'NODE_DELETE', parentName: 'readingListAssetsConnection', connectionName: 'readingListAssetsConnection', deletedIDFieldName: 'clientMutationId', }]; }
  30. 30. import { match } from 'react-router'; import IsomorphicRouter from 'isomorphic-relay-router'; import NYTNetworkLayer from 'network/NYTNetworkLayer'; export default async (req, res, next) => { match({ routes, location: req.url }, (err, redirectLocation, rootContainerProps) => { const data = await IsomorphicRouter.prepareData( rootContainerProps, new NYTNetworkLayer(req.gqlUrl, req.gqlConfig) ); const html = renderToString( <App> {IsomorphicRouter.render(data.props)} </App> ); ... }); };
  31. 31. Relay Modern • Found Relay - 0.3.0-alpha.9 • <QueryRenderer /> is not isomorphic • custom fetch( ) + QueryResponseCache
  32. 32. export const routeConfig = makeRouteConfig( <Route path="/" Component={App} query={AppQuery}> <Route Component={Home} query={HomeQuery} render={renderProp} /> </Route> );
  33. 33. const source = new RecordSource(); const store = new Store(source); const network = Network.create(fetchQuery); const environment = new Environment({ network, store, }); export default environment;
  34. 34. export default createFragmentContainer(CardMedia, { media: graphql` fragment CardMedia_media on Media { ...Image_media } `, });
  35. 35. import { graphql } from 'react-relay'; export default graphql` query Story_Query($id: ID!, $crop: [CropName!]!) { ...Story_viewer } `;
  36. 36. createRefetchContainer(Component, graphql`…`); this.props.relay.refetch({ searchTerm: 'tacos' });
  37. 37. export default createPaginationContainer( Term, graphql` fragment Term_viewer on Viewer { term(slug: $slug, taxonomy: $taxonomy) { id name slug } posts(term: $slug, after: $cursor, first: $count) @connection(key: "Term_posts") { edges { node { ...Post_post } cursor } pageInfo { startCursor endCursor hasNextPage hasPreviousPage } } } `, { direction: 'forward', getConnectionFromProps(props) { return props.viewer && props.viewer.posts; }, getVariables(props, { count, cursor }, fragmentVariables) { return { ...fragmentVariables, count, cursor, }; }, getFragmentVariables(vars, totalCount) { return { ...vars, count: totalCount, }; }, query: TermQuery, } );
  38. 38. this.props.relay.loadMore(10)
  39. 39. const updater = store => { const payload = store.getRootField('addComment'); const newComment = payload.getLinkedRecord('comment'); if (!newComment) { return; } const post = store.get(variables.input.post); const connection = ConnectionHandler.getConnection( post, 'Single_post_comments', { post: variables.input.post, } ); const newEdge = ConnectionHandler.createEdge( store, connection, newComment, 'CommentEdge'); ConnectionHandler.insertEdgeAfter(connection, newEdge); };
  40. 40. so now what?
  41. 41. Apollo
  42. 42. Apollo • use any Router you want • no need to customize network • SSR/Store rehydration requires very little setup
  43. 43. export default ( <Route path="/" component={App}> <Route path="/:id" component={Story} /> <Route component={Home} /> </Route> );
  44. 44. const client = new ApolloClient({ ssrMode: true, networkInterface: networkInterface(uri), fragmentMatcher, }); const app = ( <ApolloProvider client={client}> <App /> </ApolloProvider> ); getDataFromTree(app).then(() => { const html = renderToString(app); . . . });
  45. 45. const client = new ApolloClient({ initialState: window.__APOLLO_STATE__, networkInterface: networkInterface('/graphql'), fragmentMatcher, }); ReactDOM.hydrate( <App />, document.getElementById('main') );
  46. 46. @graphql(HomeQuery, { options: { variables: { crop: 'jumbo', }, }, }) export default class Home extends Component { render() { const { data: { loading, error } } = this.props; if (error) { return <Error />; } else if (loading) { return <Loading />; } const { viewer } = this.props.data; . . . } }
  47. 47. @graphql(HomeQuery, { options: { ssr: false, }, }) export default class Home extends Component { ... }
  48. 48. Apollo Options • import { gql } from 'react-apollo' • import gql from 'graphql-tag' • .graphql files • babel-plugin-inline-import-graphql-ast • graphql-tag/loader
  49. 49. export default function Media() { . . . } Media.fragments = { node: gql` fragment Media_node on Media { id url } `, };
  50. 50. export default function Media() { . . . } Media.fragments = { node: gql` fragment Media_node on Media { id url ... on Image { ...Image_node } } ${Image.fragments.node} `, };
  51. 51. #import "./Archive_posts.graphql" query Term_Query( $slug: String!, $cursor: String, $count: Int ) { viewer { term(slug: $slug) { id name slug } posts(term: $slug, after: $cursor, first: $count) { ...Archive_posts } } }
  52. 52. this.props.data.refetch({ … }) this.props.data.fetchMore({ … })
  53. 53. Isomorphic React/GraphQL
  54. 54. The Problem with HTML in React and GraphQL
  55. 55. Structured Data • Union types: BlockUnion and InlineUnion • fragments and queries can become enormous when using heterogenous types • recursion of unknown/infinite depth is a problem in GraphQL
  56. 56. Scripts in HTML • JavaScript will not execute when simply:
 
 <div dangerouslySetInnerHTML={{ __html: content }} /> • Scripts need to be parsed out of content and intialized/re- initialized on: componentDidMount / componentDidUpdate • Server-rendering scripts - nodes get blown away on 
 
 ReactDOM.{render,hydrate}
  57. 57. Roadmap • Moving to React 16, adjusting to the ecosystem • Continue following developments with Relay/Apollo • Continue evolving our schema • CSS-in-JS (!!!), Emotion and friends
  58. 58. We're Hiring! •All jobs: 
 https://nytimes.wd5.myworkdayjobs.com/NYT/ •Home team: 
 https://nytimes.wd5.myworkdayjobs.com/NYT/job/New-York-NY/ Engineer---News-Products_REQ-001556-2 •Story team: 
 https://nytimes.wd5.myworkdayjobs.com/en-US/Tech/job/New-York-NY/ Engineer---News-Products_REQ-001556 •Data team (come talk to us!): GraphQL, Scala, Finagle
  59. 59. Thank You.

×