leangen / graphql-spqr

Build a GraphQL service in seconds
Apache License 2.0
1.1k stars 182 forks source link

Directives _mappedType, _mappedInputField and _mappedOperation not in schema #290

Open pepijno opened 5 years ago

pepijno commented 5 years ago

SPQR adds the directives _mappedType, _mappedInputField and _mappedOperation to types, fields, queries and more. When doing introspection on the schema (for example with the query graphql.introspection.IntrospectionQuery.INTROSPECTION_QUERY) the three directives are not listed as part of the schema. Some query validations in apollo throw errors due to the fact that the definition of the directives is not in the schema.

Is there a way to get the definition of the directives in the schema?

kaqqao commented 5 years ago

Huh? Introspection (according to the specification) does not expose any server-side directives (directives on any schema element definition, commonly supplied via SDL). Only client-side directives are visible (directives that a client can send as a part of a query). If this somehow breaks Apollo, Apollo is breaking the GraphQL spec, and the issue should be opened there.

The 3 directives SPQR adds are strictly server-side, and the client has no business sending them, nor knowing about them. You could add directives of the same name to the client-visible list (by using generator.withAdditionalDirectives(...)), but it is semantically incorrect to do so... What would a query such as:

{
   books {
      title @_mappedType(type = ???)
   }
}

even mean?

*Just to clarify, my terminology here is my own, but by server-side directives I mean the ones allowed only on any of: SCHEMA, SCALAR, OBJECT, FIELD_DEFINITION, ARGUMENT_DEFINITION, INTERFACE, UNION, ENUM, ENUM_VALUE, INPUT_OBJECT, INPUT_FIELD_DEFINITION. By client-side, I mean the directives allowed on: QUERY, MUTATION, SUBSCRIPTION, FIELD, FRAGMENT_DEFINITION, FRAGMENT_SPREAD, INLINE_FRAGMENT.

kaqqao commented 5 years ago

Btw, there are talks in the GraphQL workgroup about introducing a way to make some server-side directives visible via introspection, but it hasn't yet gone anywhere concrete. There's a related issue in graphql-java as well.

That said, I'm still curious to understand what goes wrong. Can you provide some more info/examples? If there's a sensible workaround, I'd be open to that.

pepijno commented 5 years ago

Ah, then I think I know the problem. I try to use Apollo federation and in order to do that I use the federation-jvm repostiroty (https://github.com/apollographql/federation-jvm). The Apollo federation service gets the different schemas by using a query which fetches the SDL. Federation-jvm creates this SDL including all directives which means that ALL directives are exposed to the federation service. I created a simple application which shows this problem: https://github.com/pepijno/graphql-apollo-directives.

Unfortunately I want some directives to be exposed to the federation service so it is not possible to just simply create the SDL without directives. Is there any easy way to add the server-directives to the client-visible list? The Directives class in spqr generates a directive for each AnnotatedType.

TuomasKiviaho commented 5 years ago

I stumbled on the internals by using graphql.schema.idl.SchemaPrinter to see what was generated and wondered what these directives were and what they would mean from the clients perspective. I wonder if these is some way to exclude them from the schema before printing it.

iamlothian commented 5 years ago

I'd also like the ability to printout to file a schema that a client can use to call the server. Currently, it is full of these directives. As mentioned in #312

Marx2 commented 4 years ago

I would like to ask about federation support. I prefer spqr approach to generate schema out of entities, but I miss a good way how to connect many microservices with GraphQL endpoints with single gateway. Schema federation seems the easiest approach, but it needs some special setup not available in spqr.

kaqqao commented 4 years ago

@pepijno @TuomasKiviaho @iamlothian @Marx2 I'm very much interested in doing what's needed to make Apollo Federation work, but I unfortunately do not understand at all what would that entail. I went through Apollo docs and they make no sense to me whatsoever. They seem to require SDL and not introspection, and expose server-internal directives to the client etc, which all sounds bonkers.

Would one of you who have a real example be interested in helping me understand what's needed in SPQR?

kaqqao commented 4 years ago

As for hiding the SPQR-internal directives from the printout, there's nothing I can do about it. SchemaPrinter is coming from graphql-java and has no filtering support. I suppose if I make a PR to add the feature , they'd be willing to accept it, but it's ultimately not up to me...

Marx2 commented 4 years ago

As I understand, adding https://github.com/apollographql/federation-jvm to spqr project and making a configuration class should do the magic. I think it could be the best to make such an example in spqr source tree. Do the example, download and start Apollo federation app, and it should work. Maybe somebody had such an example. If not, I can try build one, but as I'm new in graphql world, it'll take some time

pepijno commented 4 years ago

Suppose you already have a graphql schema. What the federation-jvm does is it adds two queries to the root, _service and _entities:

type Query {
  ...
  _service: _Service
  _entities(representation: [_Any!]!): [_Entity]!
}

scalar _Any

union _Entity = ...

type _Service {
  sdl: String!
}

What gives all the trouble however is the sdl field in the _Service type. As the name suggests, sdl returns the sdl of your original graphql schema. The federation server which stitches all the different uses this sdl field to get the sdl from all different services, reads it and then combines them into one. The federation-jvm project uses the following options when printing the schema

SchemaPrinter.Options.defaultOptions()
                .includeScalarTypes(true)
                .includeExtendedScalarTypes(true)
                .includeSchemaDefintion(true)
                .includeDirectives(true);

which also prints all the directives @_mappedType, @_mappedInputField and @_mappedOperation. Unfortunately, you also use directives such as @external to tell the federation server which types can be referenced so the sdl needs to include the directives. Using the above printer options the definitions of @_mappedType, @_mappedInputField and @_mappedOperation are not included in the schema and this is what makes the federation server fail when it parses the sdl.

One possible solution might be to add the definitions of the missing directives to the schema. I've tried to do it manually but it seemed like an awfull lot of work. Another one is to indeed change the SchemaPrinter to not print the internal directives in some way or another. Or change the federation-jvm project to create the sdl in a different way?

Does this help @kaqqao ?

williamboman commented 4 years ago

Seems like https://github.com/graphql-java/graphql-java/pull/1721 is included in graphql-java v14, only thing needed now is apollographql/federation-jvm to upgrade to v14 and expose an API that allows passing options to the schema transformer which would allow you to exclude _mappedType, _mappedInputField and _mappedOperation from the SDL?

ankit-joinwal commented 4 years ago

I have opened a Pull Request in federation-jvm which will allow excluding the _mappedType, _mappedInputField and _mappedOperation directives from schema that gets printed by FederationSdlPrinter in federation-jvm library.

I have tested the changes on my local and finally able to run spqr with federation-jvm.

Here is the link for the Pull Request for reference - https://github.com/apollographql/federation-jvm/pull/80

@kaqqao it seems finally we will be able to run Apollo Federation based micro-services built with spqr.

ankit-joinwal commented 4 years ago

@williamboman

Seems like graphql-java/graphql-java#1721 is included in graphql-java v14, only thing needed now is apollographql/federation-jvm to upgrade to v14 and expose an API that allows passing options to the schema transformer which would allow you to exclude _mappedType, _mappedInputField and _mappedOperation from the SDL?

I tried to add a feature in federation-jvm to exclude those directives from being printed in SDL. See this PR https://github.com/apollographql/federation-jvm/pull/80

However, the PR isn't accepted by federation-jvm (check their responses on PR) and instead they have suggested to put the directive definitions in schema by ourselves. I have tried that and it works. I am able to use SPQR with federation-jvm and apollo gateway in front.

Is this going to break anything with SPQR? Though i have tried queries implementing above approach and they seem to work fine.

sarathm09 commented 3 years ago

As I understand, adding https://github.com/apollographql/federation-jvm to spqr project and making a configuration class should do the magic. I think it could be the best to make such an example in spqr source tree. Do the example, download and start Apollo federation app, and it should work. Maybe somebody had such an example. If not, I can try build one, but as I'm new in graphql world, it'll take some time

@Marx2 Were you able to build one example? I've been searching for some docs/examples, but couldn't find any. Any help is appreciated.

ankit-joinwal commented 3 years ago

Here is a working example of how to use SPQR with Apollo Federation https://github.com/ankit-joinwal/graphqpl-spqr-federation-poc

sarathm09 commented 3 years ago

Here is a working example of how to use SPQR with Apollo Federation https://github.com/ankit-joinwal/graphqpl-spqr-federation-poc

Thanks Ankit!

CacheControl commented 3 years ago

Thanks @ankit-joinwal, that's incredibly helpful!

It's also non-trivial to figure out; I feel very lucky to have stumbled upon that comment. I'd vote this example be promoted to the samples project and/or mentioned in the docs.

krisiye commented 3 years ago

@ankit-joinwal - Thanks for the example above. Works fine for the most part except for input type on mutations.

For example:

    @GraphQLMutation
    public UserResponse createUser(@GraphQLArgument(name = "userRequest",
            description = "request to create a user") UserRequest request) {
        return new UserResponse();
    }

Produces the following on the federated schema:

"Mutation root"
type Mutation {
  createUser(
    "request to create a user"
    userRequest: UserRequestInput
  ): UserResponse @_mappedOperation(operation : "__internal__")
}

input UserRequestInput @_mappedType(type : "__internal__") {
  id: Long! @_mappedInputField(inputField : "__internal__")
  request: RequestInput @_mappedInputField(inputField : "__internal__")
  users: [UserInput] @_mappedInputField(inputField : "__internal__")
}

Composing a graph on apollo complains about:

Directive "@_mappedType" may not be used on INPUT_OBJECT

Any thoughts?

adil-rakhimbekov commented 3 years ago

... Composing a graph on apollo complains about:

Directive "@_mappedType" may not be used on INPUT_OBJECT

Any thoughts?

@krisiye just add another validLocation like that:

GraphQLDirective mappedTypeDirective = GraphQLDirective.newDirective()
                .name("_mappedType")
                .description("")
                .validLocations(Introspection.DirectiveLocation.INPUT_OBJECT, Introspection.DirectiveLocation.OBJECT)
                .argument(GraphQLArgument.newArgument()
                        .name("type")
                        .description("")
                        .type(unrepresentableScalar)
                        .build()
                )
                .build();
krisiye commented 3 years ago

@adil-rakhimbekov - Perfect. That was what I was looking for. Worked great! Thank you for your help on this one!