graph-quilt / graphql-orchestrator-java

GraphQL Orchestrator stitches the schemas from multiple micro-services and orchestrates the graphql queries to these services accurately at runtime
https://graph-quilt.github.io
Apache License 2.0
70 stars 31 forks source link

Schema Validation problems in Schema Generated by getExecutableSchema #198

Open shamin2021 opened 4 months ago

shamin2021 commented 4 months ago

Describe the bug When I use orchestrator and merge two subgraphs with Apollo Federation Directives as given below ,

String inventorySchema = "type Product @key(fields: \"upc\") @extends {\n" +
                "    upc: String! @external\n" +
                "    weight: Int @external\n" +
                "    price: Int @external\n" +
                "    inStock: Boolean\n" +
                "    shippingEstimate: Int @requires(fields: \"price weight\")\n" +
                "}";
String productSchema = "type Query {\n" +
      "    topProducts(first: Int = 5): [Product]\n" +
      "    productB (upc : String!): Product\n" +
      "}\n" +
      "\n" +
      "type Product @key(fields: \"upc\") {\n" +
      "    upc: String!\n" +
      "    name: String\n" +
      "    price: Int\n" +
      "    weight: Int\n" +
      "}\n";
String reviewSchema = "type Review @key(fields: \"id\") {\n" +
      "    id: ID!\n" +
      "    body: String\n" +
      "    product: Product @resolver(field:\"productB\", arguments: [{name : \"upc\", value: \"UPC001\"}])\n" +
      "}\n" +
      "\n" +
      "type User @key(fields: \"id\") @extends {\n" +
      "    id: ID! @external\n" +
      "    username: String @external\n" +
      "    reviews: [Review]\n" +
      "}\n" +
      "\n" +
      "type Product @key(fields: \"upc\") @extends {\n" +
      "    upc: String! @external\n" +
      "    reviews: [Review]\n" +
      "}\n" +
      "\n" +
      "# ================================\n" +
      "# define this as built-in directive\n" +
      "directive @resolver(field: String!, arguments: [ResolverArgument!]) on FIELD_DEFINITION\n" +
      "\n" +
      "# define this as built-in type\n" +
      "input ResolverArgument {\n" +
      "    name : String!\n" +
      "    value : String!\n" +
      "}";
String userSchema = "type Query {\n" +
      "    me: User\n" +
      "}\n" +
      "\n" +
      "type User @key(fields: \"id\") {\n" +
      "    id: ID!\n" +
      "    name: String\n" +
      "    username: String\n" +
      "}";

// Creating providers for your GraphQL services
GenericProvider inventoryService = new GenericProvider("http://localhost:8084/graphql", httpClient,
      inventorySchema, "inventory");
GenericProvider productService = new GenericProvider("http://localhost:8081/graphql", httpClient,
      productSchema, "product");
GenericProvider reviewService = new GenericProvider("http://localhost:8083/graphql", httpClient,
      reviewSchema, "review");
GenericProvider accountService = new GenericProvider("http://localhost:8082/graphql", httpClient,
      userSchema, "user");

RuntimeGraph runtimeGraph = SchemaStitcher.newBuilder()
      .service(accountService)
      .service(productService)
      .service(inventoryService)
      .service(reviewService)
      .build()
      .stitchGraph();

The stitched Schema when output through

String printSchema = new SchemaPrinter().print(runtimeGraph.getExecutableSchema());
 System.out.println(printSchema);

produces the output

"Directs the executor to include this field or fragment only when the `if` argument is true"
directive @include(
    "Included when true."
    if: Boolean!
  ) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

"Directs the executor to skip this field or fragment when the `if`'argument is true."
directive @skip(
    "Skipped when true."
    if: Boolean!
  ) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

directive @provides(fields: String!) on FIELD_DEFINITION

"Exposes a URL that specifies the behaviour of this scalar."
directive @specifiedBy(
    "The URL that specifies the behaviour of this scalar."
    url: String!
  ) on SCALAR

directive @extends on OBJECT | INTERFACE

directive @deprecated(reason: String = "No longer supported") on FIELD_DEFINITION | ENUM_VALUE

directive @key(fields: _FieldSet) repeatable on OBJECT | INTERFACE

directive @requires(fields: _FieldSet!) on FIELD_DEFINITION

directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

directive @tag(name: String!) repeatable on SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION

directive @external on FIELD_DEFINITION

"[product]"
type Product {
  inStock: Boolean
  name: String
  price: Int
  reviews: [Review]
  shippingEstimate: Int
  upc: String!
  weight: Int
}

"[]"
type Query {
  _namespace: String
  me: User
  productB(upc: String!): Product
  topProducts(first: Int = 5): [Product]
}

"[review]"
type Review @key(fields : "id") {
  body: String
  id: ID!
  product: Product @resolver(arguments : [{name : "upc", value : "UPC001"}], field : "productB")
}

"[user]"
type User {
  id: ID!
  name: String
  reviews: [Review]
  username: String
}

"A selection set"
scalar _FieldSet

"[review]"
input ResolverArgument {
  name: String!
  value: String!
}

When this is parsed

SchemaParser parser = new SchemaParser();
        TypeDefinitionRegistry typeDefinitionRegistry = parser.parse(Paths.get("src/main/resources/supergraph.graphqls").toFile());
        RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring().build();
        GraphQLSchema federatedSchema = Federation.transform(typeDefinitionRegistry, runtimeWiring)
                .build();
        System.out.println(federatedSchema);

There are multiple issues that arise as below ,

SchemaProblem{errors=['product' [@50:3] tried to use an undeclared directive 'resolver']}

SchemaProblem{errors=['include' type [@28:1] tried to redefine existing directive 'include' type [@1:1], 'skip' type [@30:1] tried to redefine existing directive 'skip' type [@6:1]]}

When I remove the duplicate entries for @include and @skip and add the directive @resolver(field: String!, arguments: [ResolverArgument!]) on FIELD_DEFINITION

the validation is successful

Can you please let me know how this can be fixed.

I further want to know if this stitched graph can be considered and treated as a supergraph similar to the one in APOLLO Federation.

ashpak-shaikh commented 4 months ago

RuntimeGraph is supposed to hold the executable schema for you. The expectation is you will use this part of the schema to execute the request and not reparse it. Why are you trying to parse the schema again after the runtime graph is constructed?

shamin2021 commented 4 months ago

Sorry for the confusion , I just need to confirm,

Question 1 : Is the runtimeGraph.getExecutableSchema() complying with the schema parser or not.

SchemaParser parser = new SchemaParser();
TypeDefinitionRegistry typeDefinitionRegistry = parser.parse(<generated runtimeGraph.getExecutableSchema() >);

Question 2 : How can I generate the supergraph Schema, when the subgraphs are provided using the orchestrator-library.

ashpak-shaikh commented 4 months ago

runtimeGraph.getExecutableSchema() is the supergraph schema. you are printing it correctly, but that doesn't print the directive definition by default, Look at the options it takes it to print the complete schema https://github.com/graphql-java/graphql-java/blob/master/src/main/java/graphql/schema/idl/SchemaPrinter.java

I am curious why do you want to parse it again and going back from executable schema to TypeDefintiionRegistry.

The standard path in graphql-java is TypeDefintionRegistry and then executable schema. Here you already have an executable schema.

You can create an instance of GraphQLOrchestrator and use the runtimegraph to execute queries on it directly