Skip to content
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

How to pass operationName to stitched service calls? #1924

Closed
tomasstrejcek opened this issue Aug 19, 2020 · 20 comments
Closed

How to pass operationName to stitched service calls? #1924

tomasstrejcek opened this issue Aug 19, 2020 · 20 comments

Comments

@tomasstrejcek
Copy link
Contributor

Hi, could anybody provide guidance how to get to the old behavior when the operationName from frontend calls were passed to the services behind the gateway? (you need it for data collections, traces, apollo engine etc to be able to efficiently debug and tweak calls)

Was trying a lot of different approaches mentioned in #1700 but nothing doesnt seem to make it work - info object doesnt contain the original operation name, so custom executor doesnt work, same for custom createProxyingResolver. If I patch it via context and add operationName to the execution itself

return toPromise(execute(ApolloLink.from([ contextLink, errorLink, links ]), {
      operationName: info?.operation?.name?.value || context?.operationName,
      query: document,
      variables,
      context: {
        graphqlContext: context,
        graphqlResolveInfo: info,
        clientAwareness: {}
      }
    }))```

I am getting error "Unknown operation named 'xxx'" because the document doesnt contain the operation name anymore.

Any idea how to approach this? Thanks
@yaacovCR
Copy link
Collaborator

@tapaderster @smyrick do you have a code sample

The way to go I believe is a custom createProxyingResolver that sets the operationName property within the delegateToSchema call using the info argument.

@yaacovCR
Copy link
Collaborator

See #1651

@tomasstrejcek
Copy link
Contributor Author

tomasstrejcek commented Aug 19, 2020

working solution - is there no "cleaner" way to get the operation name than for example storing it in request context as I do and then using that to set the delegated operation name? the info object doesnt contain it

const executor = linkToExecutor(ApolloLink.from([ contextLink, errorLink, links ]))

  const schema = await introspectSchema(executor)
  return wrapSchema({
    schema,
    executor,
    createProxyingResolver: ({
                                    schema,
                                    operation,
                                    transforms,
                                    transformedSchema
                                  }: ICreateProxyingResolverOptions): GraphQLFieldResolver<any, any> => {
      return (_parent, _args, context, info) =>
        delegateToSchema({
          schema: schema,
          operationName: context?.operationName,
          operation,
          context,
          info,
          transforms,
          transformedSchema
        })
    }
  })

@tomasstrejcek
Copy link
Contributor Author

once we agree on a good solution I will try to do pr for docs https://www.graphql-tools.com/docs/remote-schemas/ - also there is an error in docs as to the options prop which is not schemaOrSubschemaConfig but is named only schema

@yaacovCR
Copy link
Collaborator

Not sure why you have to store in context, should be under info.operation.name.value, no?

@yaacovCR
Copy link
Collaborator

Thanks for pointing out error in docs!!!

@tomasstrejcek
Copy link
Contributor Author

tomasstrejcek commented Aug 19, 2020

well it looks like the info contains the transformed delegated query already, so thats why I choose the path of least resistance and went the context way, even though I dislike it :)

operation: {
  kind: 'OperationDefinition',
  operation: 'query',
  name: undefined,
  directives: undefined,
  variableDefinitions: [
    {
      kind: 'VariableDefinition',
      variable: [Object],
      type: [Object],
      defaultValue: [Object],
      directives: [],
      loc: [Object]
    }
  ],
  selectionSet: { kind: 'SelectionSet', selections: [ [Object] ] }
}

@yaacovCR
Copy link
Collaborator

info in subschema will contain transformed query but createProxyingResolver should give a delegating resolver on gateway that has access to gateway info with original query.

Although my brain could be fritzing again ...

@tomasstrejcek
Copy link
Contributor Author

tomasstrejcek commented Aug 19, 2020

well the dump is from here

return wrapSchema({
    schema,
    executor: OperationExecutor,
    createProxyingResolver: ({
                               schema,
                               operation,
                               transforms,
                               transformedSchema
                             }: ICreateProxyingResolverOptions): GraphQLFieldResolver<any, any> => {
      return (_parent, _args, context, info) => {
        console.log('operation', info?.operation)
        return delegateToSchema({
          schema,
          operationName: context?.operationName,
          operation,
          context,
          info,
          transforms,
          transformedSchema
        })
      }
    }
  })

so its not there or I am doing something totally wrong :D

@yaacovCR
Copy link
Collaborator

That's weird!! Can you set up a code sandbox?

@smyrick
Copy link
Contributor

smyrick commented Aug 19, 2020

@yaacovCR We have not yet migrated to the latest version of graphql-tools. We made a fork from v5 in our private repo and updated the logic to include the operation name when we raised #1651, so we didn't have to change the code we had.

So I actually don't yet if the changes in #1700 solve our issues

@yaacovCR
Copy link
Collaborator

See: https://codesandbox.io/s/strange-flower-3b24l?file=/index.js

Works for me. Using the following code:

const schema = makeExecutableSchema({
  typeDefs: `
    type Query {
      field: String
    }
  `,
  resolvers: {
    Query: {
      field: (_root, _args, _context, info) => {
        return info.operation.name.value;
      }
    }
  }
});

const stitchedSchema = stitchSchemas({
  subschemas: [
    {
      schema,
      createProxyingResolver: ({
        schema,
        operation,
        transforms,
        transformedSchema
      }) => (_parent, _args, context, info) =>
        delegateToSchema({
          schema,
          operationName: info.operation.name.value,
          operation,
          context,
          info,
          transforms,
          transformedSchema
        })
    }
  ]
});

Sending in:

query NamedOperation {
  field
}

to the gateway sends the operation name to the subschema, which it set up to return it:

{
  "data": {
    "field": "NamedOperation"
  }
}

@yaacovCR
Copy link
Collaborator

Similar flow for wrapSchema: see https://codesandbox.io/s/objective-cohen-p7pnf?file=/index.js and below.

What are you doing with the wrapped schema? Maybe the operation name is lost prior to that point?

const schema = makeExecutableSchema({
  typeDefs: `
    type Query {
      field: String
    }
  `,
  resolvers: {
    Query: {
      field: (_root, _args, _context, info) => {
        return info.operation.name.value;
      }
    }
  }
});

const wrappedSchema = wrapSchema({
  schema,
  createProxyingResolver: ({
    schema,
    operation,
    transforms,
    transformedSchema
  }) => (_parent, _args, context, info) =>
    delegateToSchema({
      schema,
      operationName: info.operation.name.value,
      operation,
      context,
      info,
      transforms,
      transformedSchema
    })
});

@tomasstrejcek
Copy link
Contributor Author

well, I am combining local schema with several remote schemas, so i wrap the remote schemas (and info does not contain the original operation) a then stitch them all together, is that not correct? it works just fine :)

@yaacovCR
Copy link
Collaborator

Ah. That works, but you are getting additional rounds of unnecessary delegation/churn, you can just put the subschema config for the remote schema directly into the subschemas property within the stitchSchemas call, each subschema that wants to preserve operationName can then set the operationName as above, you can also set different operation names based on the original name and the subschema you are delegating to...

@tomasstrejcek
Copy link
Contributor Author

oh, sweet :) I have seen you mention it like that somewhere, but the docs are not very specific on the matter (or I have overlook it as they are so big)

@tomasstrejcek
Copy link
Contributor Author

works as you have described it, thanks! :)

@tomasstrejcek
Copy link
Contributor Author

btw I use stitching this way, would it be wothwhile to just pust the local schemas into the subschemas a omit the mergeTypeDefs?

return stitchSchemas({
    subschemas: schemas, // these are the remote subschemas
    typeDefs: localTypes, //  mergeTypeDefs of local schemas which are gql`docs`
    mergeDirectives: true,
    resolvers: await resolversFactory(schemasMap, pubsub)
  })

@tomasstrejcek
Copy link
Contributor Author

(regards #1930)

@yaacovCR
Copy link
Collaborator

Your way is more performant I believe, as those types do not require delegation at all.

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

No branches or pull requests

3 participants