Classic, Material Design, PWA • Docker, Ansible, and other buzzwords • No DB: Twitter OAuth + User session + API calls • Simple GraphQL queries My first GraphQL application – still using it every day August 2016
to research, bootstrap, and try to use GraphQL. After that they gave a presentation to one the most experienced Ruby developers I’ve ever worked with. First attempt to use it at my previous work August 2016 March 2017
Fields • Resolvers • N+1 queries • Relay Classic • Nodes • Connections • Edges • Cursors • Mutations • Error handling First attempt to use it at my previous work August 2016 March 2017
were no best practices for server-side with Ruby • Relay Classic was the most popular option for client-side. But too opinionated, one size doesn’t fit all • Nobody understood the most popular graphql-batch gem with promises to avoid N+1 queries, sorry Shopify :) • GraphQL required to write a lot of code and duplications, something similar to the existing serializers • Almost no comprehensive examples and documentation First attempt to use it at my previous work August 2016 March 2017
to move quickly and easily • Agile software development – advocates adaptive planning, evolutionary development, early delivery, and continuous improvement, and it encourages rapid and flexible response to change August 2016 March 2017 July 2017
the data structures of our server responses. E.g. enums – a particular set of allowed values • Allows to validate client-side queries against the GraphQL schema during build time! • We can generate type declarations for our client apps (e.g. Flow types for JavaScript with apollo-codegen)
new interfaces which make sense • Our existing data structures can stay the same, e.g. ActiveRecord models • We can maintain and keep our existing RESTful APIs indefinitely
data they need in a single query – better performance, especially for mobile • Can control maximum query depth or maximum query complexity per field • Easies to instrument performance metrics per field
publicly available information • Field descriptions for documentation • Schema, integration, and unit tests GraphQL alpha version August 2016 March 2017 July 2017
instead of infinite scrolls like Facebook does (so and Relay) • We don’t have real-time item count changes on those pages very often • Much simpler to implement and understand (connection, pageInfo, cursor, first / after arguments, edge, node) Limit / offset pagination instead of cursor-based https://dev-blog.apollodata.com/understanding-pagination-rest-graphql-and-relay-b10f835549e7
at /graphql/beta • More query types, interfaces, enums, connections • Maximum query depth, arg validations, tiny mutation • Viewer (aka current_user) • First contributions by people from other teams in the company. The barrier of entry lower for new devs, IMO GraphQL beta version August 2017
HTTP requests, etc. • Batching is isolated and lazy, load data in batch where and when it's needed • Automatically caches previous queries (identity map) • Thread-safe • No need to share batching through variables or custom defined classes • No dependencies, no monkey-patches, no extra primitives such as Promises
!types[PostType] do resolve: ->(obj, args, ctx) { Post.all } end end PostType = GraphQL::ObjectType.define do name "Post" field :user, !UserType do resolve: ->(post, args, ctx) do # N + 1 post.user # queries end end end UserType = GraphQL::ObjectType.define do name "User" field :name, !types.String end
!types[PostType] do resolve: ->(obj, args, ctx) { Post.all } end end PostType = GraphQL::ObjectType.define do name "Post" field :user, !UserType do resolve: ->(post, args, ctx) do BatchLoader.for(post.user_id).batch do |ids, loader| User.where(id: ids).each { |u| loader.call(u.id, u) } end end end end UserType = GraphQL::ObjectType.define do name "User" field :name, !types.String end BatchLoader.for(post.user_id).batch do |ids, loader| User.where(id: ids).each { |u| loader.call(u.id, u) } end
!types[PostType] do resolve: ->(obj, args, ctx) { Post.all } end end PostType = GraphQL::ObjectType.define do name "Post" field :user, !UserType do resolve: ->(post, args, ctx) do BatchLoader.for(post.user_id).batch do |ids, loader| User.where(id: ids).each { |u| loader.call(u.id, u) } end end end end UserType = GraphQL::ObjectType.define do name "User" field :name, !types.String end BatchLoader.for(post.user_id).batch do |ids, loader| User.where(id: ids).each { |u| loader.call(u.id, u) } end post.user_id u.id
data: { createTodo } }) => { // Read the data from our cache for this query. const data = proxy.readQuery({ query: TodoAppQuery }); // Add our todo from the mutation to the end. data.todos.push(createTodo); // Write our data back to the cache. proxy.writeQuery({ query: TodoAppQuery, data }); }, }); Apollo Client 2: direct cache access https://www.apollographql.com/docs/react/features/caching.html // Read the data from our cache for this query. const data = proxy.readQuery({ query: TodoAppQuery }); // Write our data back to the cache. proxy.writeQuery({ query: TodoAppQuery, data });