Tag: Performance

Can GraphQL Call Itself?

graphqlIn GraphQL, an async resolver function resolves a query, mutation or a single field on a GraphQL type.  Each resolver function is passed 3 arguments:

  • object – the object you are resolving
  • args – the args passed to the field or query
  • context – a context for resolving queries, mutations or fields

The context argument can contain any information you put in it when starting to resolve a top level query or mutation in graphql.  Most of the time, this context contains DataLoaders, or a database connection or authentication information.

Typically the async resolver function will use the context to connect to a database or REST api, get or modify the data using the database or REST api and then return the necessary information.

Include graphql In The graphql Resolver Context 

Recently I did something that ended up being very useful for some of the complex work we were working on.  I added graphql to the context used when resolving a function in graphql.  Doing so made it possible to call graphql while resolving a graphql request and to make use of the same DataLoaders and other context information.

An Example – copyToDoList

To see the value in having graphql be able to call graphql, lets consider a simple ToDo list application and API.  Each User can have a ToDoList.  Each ToDoList can have ToDoItems.  When resolving a mutation to copyToDoList(id), it might be easiest to simply query for the existing ToDoList using the existing graphql query getToDoList(id), remove all the ids from the returned ToDoList and all the associated ToDoItems and then save the ToDoList and all the ToDoItems with no ids under a new name by calling the mutation to saveToDoList(ToDoList) which would save the list and list items and assign ids to all the entities and return the result.

We just implemented the copyToDoList mutation resolver by calling the getToDoList graphql query, then calling the saveToDoList graphql mutation and returning the result.

Conclusion

There are a number of benefits of enabling this type of functionality inside graphql resolvers.  First, it gives you the option to implement a resolver function by calling your own graphql queries or mutations, if that is the fastest or easiest way to do it.  Second, it allows your resolver function to deal with external names for entities and fields instead of the underlying names from database tables or column.  Third, it makes it possible to leverage existing graphql resolvers.  Finally, it compounds the performance savings by sharing the same DataLoaders for any queries or mutations you call while resolving which could result in fewer round trips to the database or REST api to get or modify the underlying data.

Advertisement

Creating A Public GraphQL API

graphql2017 saw the creation of several high profile public GraphQL APIs.  These GraphQL APIs are great for customer integrations, third-party integrators,  professional services and internal consumption.  When creating a public GraphQL API there are several important considerations.

Surface Area – Keep It Skinny

Deciding how much surface area to expose is important.  Exposing a too-limited set of apis and properties can result in an API that isn’t usable by some of your key consumers.  Exposing too many APIs and properties leaves you on the hook to support those APIs that you didn’t expect people to use, or in ways you didn’t expect them to be used.  The approach we took was to keep it skinny.  It isn’t just easier to add functionality when it becomes necessary.  Keeping the API skinny gives you a growth path and prepares API consumers for the continuous evolution which all public APIs inevitably go through.

Naming Conventions – Consistency Matters

Locking down simple naming conventions early is very important.  While the API is going to evolve, you don’t want to torture your customers with a continuous renaming of public APIs through new query/mutation names and the slow deprecation of old query/mutation names.  The naming conventions we settled on were:

  • createEntity, updateEntity, deleteEntity for mutation names
  • saveEntity for complex entity operations (create it if it doesn’t exist or update if it already exists)
  • entity to query an entity by id and entities to query entities using a variety of search criteria
  • Entity for type definition names
  • SearchEntity for the search criteria for a particular entity type

Deprecating Fields – Avoid Surprises

We use deprecated fields as a way to tag things that are going to go away.  The goal should be to keep deprecated fields for a couple major releases after tagging them as deprecated so you don’t surprise consumers when they disappear.  We also use deprecated fields as a way to tag experimental features.  These are warnings that the API is very likely to change and/or might only be partially implemented.  Again, the goal is to give consumers a hint so there are no surprises.

Architect For Reuse

Try to build architectural components that aid in resolving queries from similar data sources.  In our case, 60% of our current functionality uses existing REST APIs under the covers.  It was possible to build helpers to leverage the similarities that the REST APIs share so each query resolver, field resolver, sort order, etc. can leverage these reusable helpers instead of treating the implementation of each resolver as the first time.  We were able to build architectural components to help getting data from REST, direct SQL, Cypher queries to Neo4J and even another GraphQL endpoint.

Think About Testing

I recently blogged about our approach for creating a GraphQL integration testing framework.  Think about testing as you build out your API.  We created unit tests as developed the API, schema snapshot tests to catch accidental schema changes, and eventually full GraphQL integration tests that are completely independent of external datasources.  These tests will give you confidence that you haven’t broken things for your consumers as you iterate and improve your internal architecture.

Evangelize It

Teaching consumers about your awesome new GraphQL API is something you will want to do.  What is the point of developing such a wonderful and easy to use API if your customers, third-party integrators, professional services or internal developers don’t use it?  My advice for evangelizing any technology is the same – keep it simple.  GraphQL is new and often misunderstood.  Explain how your GraphQL API makes it super easy to get or manipulate data.  Explain how the hierarchical nature of GraphQL query results saves consumers from having to stitch results from several REST or DB calls together on the client side.  Keep it simple and approachable and pretty soon you’ll have consumers asking for enhancements, performance improvements and finding new and exciting ways to leverage your data!

 

Improving GraphQL Performance

graphql

Providing Easy Access To Data

GraphQL is a data query language and a fantastic middleware layer that reduces round trips from the browser to get data and seamlessly hides the complexity of getting data from disparate data sources including REST apis, sql, nosql and graph databases as well as any other type of data storage.

Performance Can Be A Problem

Those are the upsides. The downside is that because it is so easy to get data from various sources it is possible to write GraphQL queries that generate hundreds or thousands of calls for underlying data.

DataLoaders Save The Day

GraphQL provides a mechanism to deal with all of these requests in a really elegant way. DataLoaders offer a way to load data for some key, which can be any string or id or object.  The DataLoader de-duplicates requests for identical information but they can do even better than that.  DataLoaders are invoked to load data for a single key (via the load function) or for multiple keys at a time (via the loadMany function).  Regardless of how the DataLoader is invoked, it queues up requests for data and only makes the request after all requests have been queued or after some arbitrary limit is hit.  This means that the requests for data can be consolidated into a single batch request for data.  Instead of asking for each piece of data by id, you can ask for all pieces of data for a set of ids.

These two properties of data loaders, de-duplication and batching, make them the perfect tool for improving GraphQL query performance.

Real World Example

Using DataLoaders, I recently was able to cut a heavy GraphQL query from taking 45 minutes and making hundreds of REST and thousands of sql database calls down to 4 seconds, making 20 REST and 20 sql database calls for data.

Conclusions

When you are looking to improve your GraphQL query performance, start by looking at the underlying calls for data.  Using DataLoaders you can de-duplicate and consolidate those calls for data and dramatically reduce query time.