Skip to content

Feature/custom resolve field #2870

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed

Conversation

emaciel10
Copy link

@emaciel10 emaciel10 commented Dec 14, 2020

We are looking for the ability to cache certain execute operations, specifically the resolveField calls for a large and complex resolvers that can run very frequently since they currently trigger on GraphQL subscriptions.
We have built out that caching logic in our own codebase but currently have no place to make use of this cache since the resolveField function is not currently exported.

This is a first pass attempt at adding a bit more flexibility to graphql-js execution allowing us to cache the specific resolveField results that are important to our server while still falling back on the standard resolveField function for any paths that we know do not need caching.

It is very possible that this is already achievable with graphql-js but after looking through the logic in execute.js I did not see any way of achieving this without providing some kind of optional custom resolve field function.

The main goal here is to help us address some performance concerns that have been previously raised here #723 by adding in some custom application specific caching where it seemed the most expensive operations were being repeatedly performed.

If there is another way of achieving this custom caching behavior I would really love to see if we could make it work with our current use case!

Here is a code snippet from our custom-resolve-field.ts file that illustrates a basic example of our caching logic:

import { resolveField } from 'graphql/execution/execute';
import TTLCacheMap from '../utils/ttl-cache-map';

const schemaWeakMap = new WeakMap<any, any>();

export const customResolveField: typeof resolveField = (
  exeContext,
  parentType,
  source,
  fieldNodes,
  path
) => {
  if (path.key === 'doc' && source.doc) {
    let cachedDocResolves = schemaWeakMap.get(exeContext.schema);
    if (!cachedDocResolves) {
      cachedDocResolves = new TTLCacheMap(0);
      schemaWeakMap.set(exeContext.schema, cachedDocResolves);
    }
    const cachedRes = cachedDocResolves.get(source.doc._id);
    if (cachedRes) {
      return cachedRes;
    }

    const result = resolveField(exeContext, parentType, source, fieldNodes, path);
    cachedDocResolves.set(source.doc._id, result);
    return result;
  }

  return resolveField(exeContext, parentType, source, fieldNodes, path);
};

@emaciel10 emaciel10 closed this Dec 14, 2020
@emaciel10 emaciel10 reopened this Dec 14, 2020
@IvanGoncharov
Copy link
Member

@emaciel10 Thanks for the PR 👍
From your code example, it looks like you need to cache only doc fields.
Can you just do something like this? (not tested)

for (const type of Object.values(schema.getTypeMap())) {
  if (isObjectType(type)) {
    for (const [name, field] of Object.entries(type.getFields())) {
      if (name === 'doc') {
        const oldResolve = field.resolve;
        field.resolve = (...args) => {
          const source = args[0];
          // ...
          return oldResolve(...args);
        }
      }
    }
  }
}

@emaciel10
Copy link
Author

Hey Ivan, thanks for the suggestion, I'll take a look and see if we can override the resolve to drop in our custom caching at that level instead!

Base automatically changed from master to main January 27, 2021 11:10
@yaacovCR
Copy link
Contributor

Closing due to inactivity. Please feel free to reopen as necessary.

@yaacovCR yaacovCR closed this Oct 10, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants