Upgrade to Pro — share decks privately, control downloads, hide ads and more …

GraphQL: A SurveyMonkey Journey by Andrew Kanda...

Apollo GraphQL
May 22, 2019
220

GraphQL: A SurveyMonkey Journey by Andrew Kandalaft, Software Engineer at SurveyMonkey

Apollo GraphQL

May 22, 2019
Tweet

Transcript

  1. 2 I’m Andrew Software Engineer @ Work with APIs often!

    • Developer Platform • Web Platform • Security Engineering
  2. 4 Spoiler: It worked! Old API Egress New API Egress

    75% reduction of API payload size ~43% Desktop faster time to interaction
  3. 6 NodeJS Apollo WDS ReactJS Python Pyramid RabbitM Q Tech

    Stack ... Monorepository Microservices
  4. 8 Modular components • Reusable components • Data needs are

    colocated • Decide on component boundaries • Avoid page-level queries
  5. 9 Shared Component Shared Component Page 1 Page 2 Page

    3 Overfetching! Data Needs Underfetching! Underfetching!
  6. 11 Network Costs... • By default, every component will make

    their own network request • Minimal round-trips was one of our selling points of GraphQL • Many components need (at least partially) the same data Simple solution But not perfect...
  7. 13 Schema Design New Graph, old REST APIs... Adopt incrementally

    based on needs Design based on the domain, not existing REST APIs Focus on relationships between objects
  8. 15 Schema Design Domain Knowledge • Schema developers ideally have

    both: • Strong domain knowledge • GraphQL understanding and experience • Separate schema into domains • Apollo Gateway an option now! • We still want one unified Graph • That is consistent • Doesn’t overlap • Still have core GraphQL team
  9. 16 Confidential Resolving Data Modular reducers Client Query Server Resolvers

    const QUERY = gql`{ survey(id: $surveyId) { id status collectors { id name } } surveyCollectors(surveyId: $surveyId) { id name } }`; const resolvers = { Survey: { collectors: (survey, args, ctx, info) => getSurveyCollectors(ctx, survey.id) status: (survey, args, ctx, info) => getSurveyStatus(survey.id) } Query: { surveyCollectors: (root, args, ctx, info) => getSurveyCollectors(ctx, args.surveyId) } }
  10. 17 Confidential Resolving Data Minimize Network Requests (again…) De-Dupe Requests

    Batch Requests class SurveysAPI extends DataSource { questionLoader = new DataLoader(questionIds => { return this.get( `/survey_questions?ids=${questionIds.join(',')}` ); }); getQuestionsBatch = async questionId => this.questionLoader.load(questionId); } class DataSource { constructor(...) { ... this.dataLoader = new DataLoader(keys => Promise.resolve(keys.map(this.fetchFromKey)) ); } get = async (path, options) => { options.method = 'GET'; return this.dataLoader.load( JSON.stringify({ path, options }) ); } }
  11. 19 Distributed Tracing Step 1: Add to APM Desktop Need

    to track the request through the whole system... Step 2: Fix transactions • Option 1: OperationName (sideBarQuery) • Option 2: Parent.field (Query.survey)
  12. 20 Instrument Resolvers Add context to transaction function resolverMW(resolve, parent,

    args, context, info) { // Start a segment for each resolver in NewRelic return newrelic.startSegment( `GraphQL/${info.parentType}.${info.fieldName}`, // Include the segment as a custom metric true, // The function being tracked, wrap the resolver () => Promise.resolve(resolve(parent, args, context, info)); ); }
  13. 21 Errors & Logging Avoid sending PII with Type System!

    Promise.resolve(resolve(parent, args, context, info)).catch(error => { // Get useful error context const errorData = { fieldName: info.fieldName, parentType: info.parentType, ... arguments: cleanArguments({ args, info }) }; // Log or push where you need it logger.error(error, errorData, 'resolver'); throw error; });
  14. 24 What’s next? Make our GraphQL API public for our

    partners Getting the rest of our core application onto GraphQL Support real-time applications with GraphQL