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

Abstract types with mercurius federation #1359

Closed
ramiel opened this issue Oct 4, 2022 · 20 comments
Closed

Abstract types with mercurius federation #1359

ramiel opened this issue Oct 4, 2022 · 20 comments
Labels
Question ❔ Not future request, proposal or bug issue Solved ✔️ The issue has been solved

Comments

@ramiel
Copy link

ramiel commented Oct 4, 2022

Describe the Bug
I'm trying to use type-graphql within the context of a federation done with graphql. I need to create a schema that is compatible with the federation spec and I'm using a custom function to do so. I cannot use any apollo helper, so here my solution (not entirely the real code, just an extract)

import { printSchemaWithDirectives as printSchema } from '@graphql-tools/utils';
import { specifiedDirectives, GraphQLFieldResolver, GraphQLScalarType, 
  GraphQLString, GraphQLNonNull, GraphQLDirective, DirectiveLocation, GraphQLSchema, 
} from 'graphql';
import {
  buildSchema,
  createResolversMap,
  BuildSchemaOptions,
} from 'type-graphql';

// Specified in the same file, omitted for brevity
const federationDirectives = [
  KeyDirective,
  ExtendsDirective,
  ExternalDirective,
  // RequiresDirective,
  ProvidesDirective,
];

export const patchSchemaExtensions = (schema: GraphQLSchema): string =>
  printSchema(schema)
    .replace('type Query {', 'type Query @extends {')
    .replace('type Mutation {', 'type Mutation @extends {')
    .replace('type Subscription {', 'type Subscription @extends {');

async function buildFederatedSchema(
  options: Omit<BuildSchemaOptions, 'skipCheck'>,
): Promise<FederatedSchemaResult> {
  const schema = await buildSchema({
    ...options,
    skipCheck: true,

    directives: [
      ...specifiedDirectives,
      ...federationDirectives,
      ...(options.directives || []),
    ],
  });

  const federatedSchema = patchSchemaExtensions(schema);

  return {
    schema: federatedSchema,
    resolvers: createResolversMap(schema) as GraphQLResolverMap,
    _graphqlSchema: schema,
  };
}

So, I use it like this

const { schema, resolvers } = await buildFederatedSchema({
    // ... all my resolvers and so on
 });

app.register(async (app) => {
  app.register(mercurius, {
    schema: schema,
    resolvers,
    federationMetadata: true,
    allowBatchedQueries: true,
    // ...other options
  });
});

This works and the gateway is able to recognise all my graphql universe. What is not working are my abstract types. The problem is that graphql looks for resolveType function but inside resolver that function is named __resolveType.
My current solution is to generate a schema good for the federation gateway, but use instead the normal schema into local mercurius.
I'm not sure what I can provide more to make this issue clear, so I'm happy to collaborate to sort this out.
Also, I'm probably doing some mistake in creating the schema for the federation, in that case I apologize 😄

Environment (please complete the following information):

  • OS: MacOS (but not relevant)
  • Node 16
  • Package version 1.2.0-rc.1
  • TypeScript version 4.8.4
@carlocorradini
Copy link
Contributor

I suppose you are using Apollo Federation V2. 
Have you seen the updated version on how to build a federated schema? 
Nevertheless, I think not relying on @apollo/subgraph and obtain a working federation can be really, (really) hard to accomplish 

@ramiel
Copy link
Author

ramiel commented Oct 8, 2022

I haven't tried actually. This version looks much efficient than the previous one and doesn't use too much of apollo. I'll have a try next week, thank you

@ramiel
Copy link
Author

ramiel commented Oct 11, 2022

I had some time to try it this morning and the new code is not working, giving me this error

TypeError: Cannot read properties of undefined (reading 'QUERY')
    at Object.<anonymous> (/[...]/node_modules/@apollo/federation-internals/src/graphQLJSSchemaToAST.ts:26:50)
    at Module._compile (node:internal/modules/cjs/loader:1105:14)
    at Module._compile (/[...]/node_modules/source-map-support/source-map-support.js:568:25)
    at Module._extensions..js (node:internal/modules/cjs/loader:1159:10)
    at require.extensions..jsx.require.extensions..js (/private/var/folders/4_/z49903r50q1418nkp5ttcypm0000gn/T/ts-node-dev-hook-05562729762978713.js:114:20)
    at Object.nodeDevHook [as .js] (/[...]/node_modules/ts-node-dev/lib/hook.js:63:13)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Module.require (node:internal/modules/cjs/loader:1005:19)
    at require (node:internal/modules/cjs/helpers:102:18)

Maybe this code works only with graphql v16?

@carlocorradini
Copy link
Contributor

@ramiel 
Yes, you are right.

V2.0 is on the way.

If you really want to use Apollo Federation V2 you must have a separate package with graphql >= 16 and then import it in your main project with graphql < 16. If you don't do something crazy, it works flawlessly. Take a look at my example here.



Ciao dall'Italia 👋

@ramiel
Copy link
Author

ramiel commented Oct 11, 2022

Yep. I need to wait for type-graphql that supports v16. I'm waiting for it in any case for other thousand reasons.

Ciao

@ramiel ramiel closed this as completed Oct 11, 2022
@ramiel
Copy link
Author

ramiel commented Oct 24, 2022

ok, now that version 2 is (almost) out I tried the new implementation to build a federated schema. This is not working either, because if I have two services and each build its own federated schema, the error is GraphQLError: Field "Query._entities" can only be defined once

@ramiel ramiel reopened this Oct 24, 2022
@carlocorradini
Copy link
Contributor

ok, now that version 2 is (almost) out I tried the new implementation to build a federated schema. This is not working either, because if I have two services and each build its own federated schema, the error is GraphQLError: Field "Query._entities" can only be defined once

Can you share the code?

@ramiel
Copy link
Author

ramiel commented Oct 26, 2022

I can share part of the code.

// buildFederatedSchema.ts
import { GraphQLSchema, specifiedDirectives } from 'graphql';
import gql from 'graphql-tag';
import { buildSubgraphSchema } from '@apollo/subgraph';
import { addResolversToSchema } from '@graphql-tools/schema';
import { IResolvers, printSchemaWithDirectives } from '@graphql-tools/utils';
import {
  buildSchema,
  BuildSchemaOptions,
  createResolversMap,
} from 'type-graphql';

/**
 * Given a schema, patches the query, mutation and subscription to work in federation
 */
export const patchSchemaExtensions = (schema: GraphQLSchema): string =>
  printSchemaWithDirectives(schema);
// printSchemaWithDirectives(schema)
//   .replace('type Query {', 'type Query @extends {')
//   .replace('type Mutation {', 'type Mutation @extends {')
//   .replace('type Subscription {', 'type Subscription @extends {');

export async function buildFederatedSchema(
  options: Omit<BuildSchemaOptions, 'skipCheck'>,
  referenceResolvers?: IResolvers,
) {
  const schema = await buildSchema({
    ...options,
    directives: [...specifiedDirectives, ...(options.directives || [])],
    skipCheck: true,
  });

  const federatedSchema = buildSubgraphSchema({
    typeDefs: gql(printSchemaWithDirectives(schema)),
    resolvers: createResolversMap(schema) as any,
  });

  if (referenceResolvers) {
    addResolversToSchema({
      schema: federatedSchema,
      resolvers: referenceResolvers,
    });
  }
  return { schema: federatedSchema };
}
// schema
export const createSchema = async (opt?: { emitSchemaFile?: boolean }) => {
  const { schema } = await buildFederatedSchema({
    resolvers: [
      ...
    ],
    container: getContainer,
    authChecker: customAuthChecker,
    orphanedTypes,
  });
  if (opt?.emitSchemaFile) {
    emitSchemaToFile(schema).catch((e) => {
      console.error(e);
    });
  }
  return { schema };
};
// server.ts
 app.register(mercurius, {
      schema: schema,
      federationMetadata: true,
      allowBatchedQueries: true,
      graphiql: false,
      context: contextProviderFn,
      persistedQueryProvider: mercurius.persistedQueryDefaults.automatic(),
      errorFormatter: errorFormatter(logger),
    });

When I load this from the gateway I get the error GraphQLError: Syntax Error: Unexpected "@"

The generated schema is the following (partial)

directive @key(
  fields: _FieldSet!
  resolvable: Boolean = true
) on OBJECT | INTERFACE

directive @requires(fields: _FieldSet!) on FIELD_DEFINITION

directive @provides(fields: _FieldSet!) on FIELD_DEFINITION

directive @extends on OBJECT | INTERFACE

directive @external(reason: String) on OBJECT | FIELD_DEFINITION

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

# Rest of the schema here

It looks these directives create the problem. I have another service that do not print the directives in the schema. What can be the problem?

@carlocorradini
Copy link
Contributor

Why do you need patchSchemaExtensions function???

@ramiel
Copy link
Author

ramiel commented Oct 26, 2022

I use it in another part of the app that prints the schema to a file, but as you can see it does nothing. Also, I'm not using that file at the moment, I just load the SDL directly from the running service. So you can ignore it

@carlocorradini
Copy link
Contributor

@ramiel
Strange... Have you seen the apollo-federation example on new-major-release branch? 
Moreover, have you updated type-graphql to next (npm install --save type-graphql@next) ?
I've tried running the example with type-graphql@next and graphql >= 16 and everything works flawlessly 😅

@ramiel
Copy link
Author

ramiel commented Oct 26, 2022

So, the code that you see is copy/pasted from that branch. type-graphql is at version 2.0.0-beta1 and graphql at version 16.6
I'm wondering where those directives are coming from.

@carlocorradini
Copy link
Contributor

@ramiel 
You wrote: It looks these directives create the problem. I have another service that do not print the directives in the schema
All services must have the required directives in the generated schema (see this for more information).
It's the task of the gateway/router to compose them into a single schema.

@carlocorradini
Copy link
Contributor

So, the code that you see is copy/pasted from that branch. type-graphql is at version 2.0.0-beta1 and graphql at version 16.6 I'm wondering where those directives are coming from.

From import { buildSubgraphSchema } from '@apollo/subgraph'

@ramiel
Copy link
Author

ramiel commented Oct 26, 2022

I see. I'm using mercurius gateway as gateway. Mercurius is complaining about the presence of @.

@ramiel
Copy link
Author

ramiel commented Oct 26, 2022

I also feel it's not the directive itself, but the schema is somehow wrong (from the perspective of mercurius)

@ramiel
Copy link
Author

ramiel commented Oct 26, 2022

What's weird is that this schema is produced by mercurius itself, as you can see by my code!

@carlocorradini
Copy link
Contributor

I've never used Mercurius.
It's possible that (currently) doesn't support Apollo Federation v2?

@ramiel
Copy link
Author

ramiel commented Oct 26, 2022

I just asked @mcollina about this: mercurius-js/mercurius#897

@ramiel
Copy link
Author

ramiel commented Oct 26, 2022

Mystery solved, mercurius doesn't support federation v2. Thanks for your help

@ramiel ramiel closed this as completed Oct 26, 2022
@MichalLytek MichalLytek added Question ❔ Not future request, proposal or bug issue Solved ✔️ The issue has been solved labels Feb 17, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Question ❔ Not future request, proposal or bug issue Solved ✔️ The issue has been solved
Projects
None yet
Development

No branches or pull requests

3 participants