Se ha denunciado esta presentación.
Utilizamos tu perfil de LinkedIn y tus datos de actividad para personalizar los anuncios y mostrarte publicidad más relevante. Puedes cambiar tus preferencias de publicidad en cualquier momento.

Building custom GraphQL tooling for your team

1.567 visualizaciones

Publicado el

One of the main reasons we started using GraphQL for data fetching in the Stripe Dashboard was the variety of off-the-shelf tooling available in the community. As we integrated, we discovered opportunities to improve the stack, and some needs that were specific to Stripe. Thankfully, GraphQL also makes it really convenient to build custom tools to fit the specific needs of your project or organization.

In this talk, you'll learn about why GraphQL is uniquely suited to building custom tooling that won’t create a huge maintenance burden. First, I’ll cover some examples of the tools we built for our GraphQL implementation at Stripe. Then, I'll go over how you can build on top of existing libraries including GraphQL.js and graphql-tools. Finally, I'll give a step-by-step guide to building a new custom tool that you could use at your own company—a script that tells you where a certain GraphQL field is used in your frontend code.

Publicado en: Tecnología
  • Inicia sesión para ver los comentarios

  • Sé el primero en recomendar esto

Building custom GraphQL tooling for your team

  1. 1. Building custom GraphQL tooling for your team Sashko Stubailo Engineering Manager, Dashboard Discovery @stubailo, sashko@stripe.com
  2. 2. 1. What has made GraphQL impactful in the Stripe Dashboard 2. Novel tools we've built internally 3. How you can build your own GraphQL tooling
  3. 3. Everything I'm about to present has been a team effort at Stripe! It takes a village to create a great development environment.
  4. 4. Developing in the Stripe Dashboard • Lots of product teams contributing at once • Needs to work together as a unified whole
  5. 5. S T R I P E D A S H B O A R D S TA C K • React • Flow • Jest • Webpack • Ruby • GraphQL and Apollo
  6. 6. PA R T 1 What does it take to make product development better at scale?
  7. 7. P R O D U C T D E V E L O P M E N T Frontend API Backend
  8. 8. P R O D U C T D E V E L O P M E N T Components API Backend State management Tests Backend Component explorer Editor CI Monitoring Type generation
  9. 9. P R O D U C T D E V E L O P M E N T Components GraphQL API Backend Apollo State Management Tests Backend Component explorer Editor CI Monitoring Type generation
  10. 10. P R O D U C T D E V E L O P M E N T Components GraphQL API Backend Apollo State Management Tests Backend Component explorer Editor CI Monitoring Type generation GQL GQL GQL GQL GQL GQL GQL
  11. 11. Components GraphQL API Backend Apollo State Management Tests Backend Component explorer Editor CI Monitoring Type generation GQL GQL GQL GQL GQL GQL GQL The most impactful technologies improve all parts of the product development process.
  12. 12. Main benefit of GraphQL for us: The wealth of community tools and content
  13. 13. T O O L S W E U S E M O S T LY O F F T H E S H E L F : • Apollo Client: Simplifies data fetching and management in React • GraphQL Ruby: Makes it easy to build a typed API • Apollo CLI: Static type generation • graphql-tools: Easy data mocking
  14. 14. Content and documentation Less content to maintain, easier to onboard new engineers
  15. 15. Why does GraphQL have such a big impact? The schema defines capabilities type Author { id: Int! firstName: String lastName: String posts: [Post] } type Post { id: Int! title: String author: Author votes: Int } type Query { posts: [Post] author(id: ID!): Author } query PostsForAuthor { author(id: "1") { firstName posts { title votes } } } Query describes requirements
  16. 16. What makes it possible to build great tools? • Tooling integrates between frontend and backend • Can rely on schema being present and correct • Stable spec and ecosystem means you can build tools once and they work for a long time The last point is why it actually makes sense to have internal tooling around GraphQL!
  17. 17. PA R T 2 Tools at Stripe
  18. 18. Tools at Stripe: Mocking Write unit tests and examples without having to call a real API • Faster, more resilient tests • Possible to develop components before backend is built • Easy to generate edge case states Frontend Fake API
  19. 19. GraphQL enables automatic mocking Because GraphQL is strongly typed, libraries like graphql-tools can generate correctly-shaped mock results automatically for any query. Any valid query Schema + mocks for each type Result of the correct shape
  20. 20. What about edge cases? A single globally mocked schema is convenient, but isn't well suited to test specific pathological cases: • Error states • Loading states • Rendering specific values
  21. 21. Per-request mocking? Allows you to specify edge cases, but a lot of boilerplate for each test. Example from the Apollo docs →
  22. 22. Best of both worlds: Global mocking with overrides const mocks = { Todo: () => ({ text: () => faker.sentence(), completed: () => faker.boolean(), }), User: () => ({ name: () => faker.findName() }) } const customResolvers = { Query: () => ({ todoItems: [ { completed: true }, { completed: false }, ] }) }; G L O B A L M O C K S O V E R R I D E S Now, results are automatically filled in from global mocks, but we can override specific values for individual tests. type Todo { text: String completed: Boolean user: User } type User { name: String } type Query { todoItems: [Todo] } S C H E M A
  23. 23. Example: default mocks In this example, we're just trying to check if the component renders correctly. The specific data isn't important, so we don't need to specify anything. const wrapper = mount( <ApolloTestProvider> <ConnectOverviewPage /> </ApolloTestProvider>, ); expect(wrapper.find('ConnectLandingPage')) .toHaveLength(1);
  24. 24. Example: Overriding fields We want to assert for specific values in the rendered component. We don't want to rely on values in the global mocks, so we specify them in the test. it('renders populated state', async () => { const customResolvers = { Merchant: () => ({ balanceSummaries: () => [{ currency: 'usd', available: 1000, pending: 2000, }], defaultCurrency: () => 'usd', }), }; const wrapper = mount( <ApolloTestProvider customResolvers={customResolvers}> <ApolloExample /> </ApolloTestProvider>, ); await jestHelpers.asyncWait(); const text = wrapper.text(); expect(text).toMatch(/Default currency: usd/); expect(text).toMatch(/Currency.*Available.*Pending/); expect(text).toMatch(/$10.*$20/); });
  25. 25. Mock for loading/error states We've also added helpers for a very common type of edge case: Errors and loading state. it('renders loading state', async () => { const wrapper = mount( <LoadingProvider> <ApolloExample /> </LoadingProvider>, ); await jestHelpers.asyncWait(); expect(wrapper.text()) .toBe('Loading...'); }); it('renders error state', async () => { const wrapper = mount( <ErrorProvider> <ApolloExample /> </ErrorProvider>, ); await jestHelpers.asyncWait(); expect(wrapper.text()) .toBe('Error! Oh no!'); });
  26. 26. GraphQL mocks for prototyping Designers and developers can combine our component system with fake data with no additional effort.
  27. 27. Mocking overview ✅ Automatic global mocks ✅ Specific overrides to test edge cases ✅ Error states ✅ Loading states ✅ Built in mocks for prototypes Read the blog post at bit.ly/graphql-mocking
  28. 28. Tools at Stripe: Schema management • There's one GraphQL schema for the Dashboard • Anyone at Stripe should be able to independently make changes • We want to rely on tooling to prevent breakages
  29. 29. Our schema.graphql file encodes the current state of the schema It's automatically generated from the API code and checked in to Git
  30. 30. Detecting breaking changes in CI Frontend builds stay in the wild for a while, so we need to be carefun about backwards compatibility.
  31. 31. Custom GraphQL tools • Stable: Built on a standard spec • Convenient: Leverage community tools like GraphQL.js, Apollo Codegen, and Babel • Tailored: Uniquely designed for our team, codebase, and development environment
  32. 32. PA R T 3 Building our own tooling
  33. 33. Let's build a GraphQL field usage finder together How would we build a tool that searches our codebase for usage of a specific GraphQL field? .jsx .jsx .jsx User.name ? GraphQL Schema
  34. 34. Let's build a GraphQL field usage finder together How would we build a tool that searches our codebase for usage of a specific GraphQL field? 1. Get the GraphQL schema 2. Find queries in our codebase 3. Search those queries for usage of the desired field
  35. 35. Getting our schema Super easy using graphql-config. With a .graphqlconfig in your repo: const { getGraphQLProjectConfig } = require("graphql-config"); const schema = getGraphQLProjectConfig().getSchema(); Accessing it is as easy as: { "schemaPath": "src/graphql/schema.graphql" }
  36. 36. Finding GraphQL queries in the codebase If we're using graphql-tag, we can look for gql template literals: github.com/apollographql/graphql-tag const ExampleQuery = gql` query ExampleQuery { currentMerchant { id, defaultCurrency balanceSummaries { currency, available, pending } } } `;
  37. 37. Finding GraphQL queries in the codebase const fs = require("fs"); const { parse } = require("@babel/parser"); // Read a file const fileContents = fs.readFileSync(filename, { encoding: "utf-8" }); // Create an AST by parsing the file using Babel const ast = parse(fileContents); Using Babel to create an Abstract Syntax Tree (AST):
  38. 38. Finding GraphQL queries in the codebase const traverse = require("@babel/traverse").default; const graphqlStrings = []; traverse(ast, { TaggedTemplateExpression: function(path) { if (path.node.tag.name === "gql") { graphqlStrings.push(path.node.quasi.quasis[0]); } } }); Looking for TaggedTemplateExpressions named "gql":
  39. 39. Useful tool: AST Explorer
  40. 40. Searching for fields in each query const { parse } = require("graphql"); const ast = parse(jsNode.value.raw); Parsing the GraphQL query:
  41. 41. Searching for fields in each query const { visit, visitWithTypeInfo, TypeInfo } = require("graphql"); const typeInfo = new TypeInfo(schema); visit(ast, visitWithTypeInfo(typeInfo, { Field(graphqlNode) { const fieldName = typeInfo.getParentType().name + "." + graphqlNode.name.value; // Now, check if it's the field we're looking for } })); Looking through all the fields:
  42. 42. Searching for fields in each query if (fieldName === fieldWeAreLookingFor) { const operationName = ast.definitions[0].name.value, const line = jsNode.loc.start.line; console.log(`${filename}:${line} ${operationName}`); } Compare to the field we're looking for and print results:
  43. 43. Searching for fields in each query const { parse, visit, visitWithTypeInfo, TypeInfo } = require("graphql"); const ast = parse(jsNode.value.raw); const typeInfo = new TypeInfo(schema); visit(ast, visitWithTypeInfo(typeInfo, { Field(graphqlNode) { const fieldName = typeInfo.getParentType().name + "." + graphqlNode.name.value; if (fieldName === fieldWeAreLookingFor) { const operationName = ast.definitions[0].name.value, const line = jsNode.loc.start.line; console.log(`${filename}:${line} ${operationName}`); } } })); The whole GraphQL part:
  44. 44. Putting it all together Once we add some plumbing to read in files and accept an argument, it looks like this: $ node graphql-field-finder.js Query.user src/components/customers/CardsSection.js:40 AllCardsQuery src/graphql/queries/UnauthedClientsQuery.js:13 UnauthedClientsQuery src/user_settings/TwoFactorAuthenticationSettings.js:46 TwoFactorSettingsQuery tests/functional/full_flows_test.jsx:401 UnauthedClientsQuery tests/lib/ApolloTestProvider.test.jsx:13 ApolloTestProviderQuery tests/lib/ApolloTestProvider.test.jsx:49 ApolloTestProviderUsernameQuery
  45. 45. R E C A P : W H AT W E U S E D • graphql-config to get the schema • graphql-tag to identify queries • Babel to extract queries from code • GraphQL.js to find fields in queries See the code at bit.ly/graphql-field-finder
  46. 46. Building custom GraphQL tooling for your team Sashko Stubailo @stubailo, sashko@stripe.com We're hiring at Stripe in Dublin, SF, Singapore, Seattle, and remote in North America! Go forth and build your own GraphQL tools to make product development better at your company! Get this presentation: bit.ly/custom-graphql-tooling

×