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

Support for Apollo Federation #351

Open
voslartomas opened this issue May 31, 2019 · 61 comments
Open

Support for Apollo Federation #351

voslartomas opened this issue May 31, 2019 · 61 comments
Assignees
Labels
Community 👨‍👧 Something initiated by a community Discussion 💬 Brainstorm about the idea Enhancement 🆕 New feature or request
Milestone

Comments

@voslartomas
Copy link

Is your feature request related to a problem? Please describe.
I guess it would be about to add some decorators for @key, @extend etc, to be able to declare federation schema services.

Describe the solution you'd like
https://blog.apollographql.com/apollo-federation-f260cf525d21

@MichalLytek MichalLytek added Blocked 🚫 Resolving this issue is blocked by other issue or 3rd party stuffs Wontfix ❌ This will not be worked on labels May 31, 2019
@MichalLytek
Copy link
Owner

I guess it would be about to add some decorators for @key, @extend etc

So it's blocked by #77 😞
But when #77 is ready, you would be able to use Apollo Federation with TypeGraphQL without any problem, so no further integration needed 🔒

@voslartomas
Copy link
Author

@19majkel94 I see, I've read the issue and saw that you agreed (february) with guys from graphql-js, but not sure what does that mean for current situation? Is it going to be blocked, or something is happening?

@MichalLytek
Copy link
Owner

MichalLytek commented Jun 1, 2019

I am waiting for changes in GraphL spec and graphql-js that add interchangeability between extensions and directives, so both approaches (SDL and code-first) could be used and handled by universal directives and other tools like prisma or apollo federation.

And @Hossein-s is working on a workaround to modify astNodes that are currently used by all JS tools to consume directives metadata from SDL.

@MichalLytek
Copy link
Owner

I guess it would be about to add some decorators for @key, @extend etc,

Looks like it's not only about directives but also the extend keyword, which is handled in #228 🔑

@j
Copy link
Contributor

j commented Jun 18, 2019

@19majkel94 is it the extend keyword in #228 though? Doesn't #228 do the whole, type User extends BaseType.. The extend keyword in federation goes before the type, it's a way they can make the root graphql service have field calls different services.

@MichalLytek
Copy link
Owner

MichalLytek commented Jun 19, 2019

@j I know, in #228 I was thinking about both merging typedefs from different file and extend type Foo syntax if it's possible with graphql-js.

Edit: extendSchema exist but I would have to also use graphql-tools to merge the schema with resolvers or find a way to separate the extends to SDL and keeps the field resolvers for unknown fields? Needs some exploration 😕

@j
Copy link
Contributor

j commented Jun 19, 2019

@19majkel94 bummer, yeah looks like most "code first" node.js implementations are going to have a rough time from what I was reading. I saw a post of someone requesting a different way of making schemas to make it easier, but I'm not sure what it'd take. Apollo Federation is the next step for our company's infrastructure and will have to move away from type-graphql if support doesn't come to, and that'll be a sad day. :(

@j
Copy link
Contributor

j commented Jun 19, 2019

graphql-nexus/nexus#148 for reference

@MichalLytek
Copy link
Owner

MichalLytek commented Jun 19, 2019

Maybe I could just drop building schema using graphql-js and create the SDL string on my own (like in the reflection plugin PoC), and then just plug in the resolvers using graphql-tools to create an executable schema if it's needed (or not in the case of the type-graphql core).

@j
Copy link
Contributor

j commented Jun 19, 2019

@19majkel94 that sounds pretty flexible? Also, again, I haven't dug into how to do it within your library. Are you looking at the SDL apollo-federation came up with? Or the actual graphql schema?

Examples:
https://pw678w138q.sse.codesandbox.io/graphql (sibling service)
https://v368r9ml47.sse.codesandbox.io/ (gateway)

You wouldn't need to do anything that the gateway does, just be able to output what the sibling does per spec.

But just outputting string schema's seems like the most flexible?

@j
Copy link
Contributor

j commented Jun 19, 2019

Another issue to help solve code first libraries: https://github.com/apollographql/apollo-server/issues/2769

@j
Copy link
Contributor

j commented Jun 19, 2019

Another note I saw regarding "extends". https://www.apollographql.com/docs/apollo-server/federation/federation-spec/

The spec actually supports this:

type User @key(fields: "id") @extends {
  id: ID! @external
  reviews: [Review]
}

But still looks like a lot of work, :(

@MichalLytek MichalLytek added Discussion 💬 Brainstorm about the idea Enhancement 🆕 New feature or request and removed Wontfix ❌ This will not be worked on labels Jun 19, 2019
@MichalLytek MichalLytek added this to the 2.0.0 release milestone Jun 19, 2019
@MichalLytek MichalLytek reopened this Jun 19, 2019
@MichalLytek MichalLytek changed the title Are you planning to implement Apollo Federation? Support for Apollo Federation Jun 19, 2019
@MichalLytek MichalLytek self-assigned this Jun 19, 2019
@MichalLytek
Copy link
Owner

MichalLytek commented Nov 3, 2019

I've added an example of apollo-federation (a modified federation demo from @j PR):
https://github.com/MichalLytek/type-graphql/tree/20c81f4f7ee82779595002d25784fce3a8ff8b9b/examples/apollo-federation

I will leave this PR open to collect some feedback about the integration.
Also in the future I plan to make @typegraphql/federation package with @FederationKey decorator and others like defining resolverReference, etc. 😉

@MichalLytek MichalLytek added Community 👨‍👧 Something initiated by a community and removed Blocked 🚫 Resolving this issue is blocked by other issue or 3rd party stuffs labels Nov 3, 2019
@lgabeskiria
Copy link

Hi guys! Thanks for this amazing library!
Do you have any ETA when can we expect this feature in production?

@MichalLytek
Copy link
Owner

@lgabeskiria Have you tried the latest beta release? Without testing and confirmation I won't release "stable" package.

@rickdgeerling
Copy link

rickdgeerling commented Mar 3, 2020

No updates. It can be enabled once the federation support in this repository is no longer beta.

Currently it would require type-graphql as a beta dependency. Tell me if I'm missing the point, but it seems strange for a stable release of @nestjs/graphql to have a beta dependency. If you can make a good argument for it, the fix is as simple as removing this line

EDIT: + updating the package.json of course

@gperdomor
Copy link

gperdomor commented Mar 3, 2020

@tuxmachine well, if we consider beta anything with version minor to 1.0.0, then TypeORM is also a beta... and as we know, is one of the best ORM for typescript, and the default ORM for nestjs 🤔

@rickdgeerling
Copy link

rickdgeerling commented Mar 3, 2020

@gperdomor No, I consider it beta because it's listed under "unreleased" in the changelogs of type-graphql, and the code enabling Directives is packaged inside [email protected]

Edit: to clear up confusion, type-graphql itself is not beta. The directives feature is.

@gperdomor
Copy link

ohh I see... In that case I think we should wait...

@henry-young
Copy link

henry-young commented Mar 12, 2020

I've been playing around with the typegrahpql federation example and I don't know how to get the auth checker to validate the @Authorized() fields on ObjectTypes resolved using the __resolveReference() function, as in the example. Is there a way to trigger the auth checks manually from the __resolveReference() function?

@bsparks
Copy link

bsparks commented May 7, 2020

@MichalLytek I have been looking into using the federation example now with rc1, I think the createResolversMap util method needs to be exposed externally in order to use it outside this repo? https://github.com/MichalLytek/type-graphql/blob/master/examples/apollo-federation/helpers/buildFederatedSchema.ts#L10

@MichalLytek
Copy link
Owner

MichalLytek commented May 7, 2020

@bsparks done in 7ddface 💪

@andrei-bitca-dc
Copy link

andrei-bitca-dc commented May 21, 2020

Hello,



I ran into a problem when trying to build the federation schema using buildFederatedSchema example - It seems that when defining default values for an input enum type property and the enum resolver has different values than keys, it doesn't recognize the default enum value:

Example:

Schema:

enum TestEnum {
   A
}

input TestInput {
  prop: TestEnum = A
}

TestEnum resolver:

enum TestEnum {
  A = "some_different_value_than_A",
}

registerEnumType(TestEnum, {
  name: "TestEnum"
});

Error on example:

GraphQLError: Enum "TestEnum" cannot represent value: "A"

Possible solution:

Change

enumMap[name] = value;
to:

enumMap[name] = name;

@MichalLytek
Copy link
Owner

@andrei-bitca-dc
It cannot be changed to enumMap[name] = name because enum values are runtime value and enum keys are enum names in schema.

This is the behavior of the official GraphQL implementation, so you should use internal values as default values:
graphql/graphql-js#1604

@andrei-bitca-dc
Copy link

andrei-bitca-dc commented May 21, 2020

@MichalLytek The problem is that it throws me this error: GraphQLError: Enum "TestEnum" cannot represent value: "A" on generating the federation schema, with the following InputType definition:

@InputType()
export class TestInput {
  @Field(_type => EnumType, { defaultValue: EnumType.A })
  public prop: EnumType = EnumType.A;
}

buildFederatedSchema.ts:

import { addResolversToSchema, GraphQLResolverMap } from "apollo-graphql";
import { GraphQLSchema, specifiedDirectives } from "graphql";
import gql from "graphql-tag";
import { BuildSchemaOptions, buildSchemaSync } from "type-graphql";
import { createResolversMap } from "type-graphql/dist/utils/createResolversMap";

import { buildFederatedSchema as buildApolloFederationSchema, printSchema } from "@apollo/federation";
import federationDirectives from "@apollo/federation/dist/directives";

export function buildFederatedSchema(
  options: Omit<BuildSchemaOptions, "skipCheck">,
  referenceResolvers?: GraphQLResolverMap<any>,
): GraphQLSchema {
  const schema = buildSchemaSync({
    ...options,
    directives: [...specifiedDirectives, ...federationDirectives, ...(options.directives || [])],
    skipCheck: true,
  });
  const federatedSchema = buildApolloFederationSchema({
    typeDefs: gql(printSchema(schema)),
    resolvers: <any>createResolversMap(schema),
  });
  if (referenceResolvers) {
    addResolversToSchema(federatedSchema, referenceResolvers);
  }
  return schema;
}

@MichalLytek
Copy link
Owner

@andrei-bitca-dc Can you reproduce that with the normal buildSchema? Maybe apollo federation is doing something wrong or they have tried to "fix" that issue

@andrei-bitca-dc
Copy link

andrei-bitca-dc commented May 21, 2020

@MichalLytek buildSchemaSync works. It doesn't throw any error.

@MichalLytek
Copy link
Owner

So please try to reproduce the case with raw graphql-js and raise the issue on theirs side 😉

@andrei-bitca-dc
Copy link

@MichalLytek FYI: https://github.com/apollographql/apollo-server/issues/4143.

@yaacovCR
Copy link

GraphQL-Tools's schema stitching now supports type merging similar to Federation, as well as specification of merge instructions for the gateway via directives (see our docs and https://github.com/gmac/schema-stitching-handbook for examples).

We want to support specification of merge instructions via extensions for code first schemas such as TypeGraphQL Just checking first if TypeGraphQL lets you specify extensions for types and fields, and want to inquire as to whether there is anything I should know about how you handle extensions within the framework. I imagine that we will follow supposed best practices and nest our type field extensions under a GRAPHQL_TOOLS_SCHEMA_STITCHING key... We will probably have options on the gateway to modify the location of the extensions as desired. Just inquiring whether there is any TypeGraphQL specific info about extensions that I should be aware of...

@MichalLytek
Copy link
Owner

@yaacovCR
Copy link

Looks pretty straightforward, thanks!

@yaacovCR
Copy link

yaacovCR commented Dec 20, 2020

Decided to go a different way and follow Gatsby/graphql-compose convention of reading directives from extensions.directives -- see graphql/graphql-js#1343 (comment) -- but I also allowed this to be customized as desired.

This is now released as canary, see ardatan/graphql-tools#2391

To get this working:

  1. Add the prebuilt directives to the schema from @graphql-tools/stitching-directives
  2. Use the directives within the schema via extensions.directives
  3. Expose the SDL to the gateway via some resolver (_root, _args, _context, info) => printSchemaWithDirectives(info.schema) where printSchemaWithDirectives is an improved @graphql-tools/utils function that follows same convention as above.

For a peek at fully worked out examples of stitching with SDL-first approaches, take a look at https://github.com/gmac/schema-stitching-handbook, where @gmac takes you through step-by-step from the basics all the way to hot reloading/versioning releases.

Hopefully, soon an example will appear with code-first approach.

Thanks!

@yaacovCR
Copy link

TypeGraphQL support is live with stitching directives:

https://github.com/yaacovCR/schema-stitching-demos/blob/code-first/code-first-schemas/services/products/schema.ts

note that although TypeGraphQL supports tagging schema entities (types, fields) with directive nodes (directive use on schema entities) via extensions, it does not seem to support adding actual directive entities (the actual definitions of custom directives).

I think this is incorrect behavior, a server might want to implement a custom directive on a query.

You can do this quite easily in graphql-js

new GraphQLSchema({
  ...
  directives: [...specifiedDirectives, ...your_array_of_custom_directives],
})

I worked around this by using extendSchema to add in more typeDefs, but see the graphql-js example where I just loaded in the actual directives. (https://github.com/yaacovCR/schema-stitching-demos/blob/code-first/code-first-schemas/services/accounts/schema.js)

@MichalLytek
Copy link
Owner

MichalLytek commented Dec 25, 2020

it does not seem to support adding actual directive entities (the actual definitions of custom directives).

I'm not sure if I understand that correctly but I think it does:

image

@yaacovCR
Copy link

Woops, fantastic!

@yaacovCR
Copy link

Example updated. We don't need skipCheck true even!

@vongohren
Copy link

vongohren commented Mar 31, 2021

So one cannnot use type graphql with apollo federation? Is it impossible or just tricky? Or why is this still open?
There exists an example here: https://github.com/MichalLytek/type-graphql/tree/master/examples/apollo-federation?

@MichalLytek
Copy link
Owner

@vongohren #351 (comment)

@tim-trisnadhama
Copy link

tim-trisnadhama commented Jul 13, 2021

I've added an example of apollo-federation (a modified federation demo from @j PR):
https://github.com/MichalLytek/type-graphql/tree/20c81f4f7ee82779595002d25784fce3a8ff8b9b/examples/apollo-federation

I will leave this PR open to collect some feedback about the integration.
Also in the future I plan to make @typegraphql/federation package with @FederationKey decorator and others like defining resolverReference, etc. 😉

Hi, is there any update on the @FederationKey? I m having trouble getting the context from the resolveReference since we inject the context (along with the request) when we initialize the server

@tim-trisnadhama
Copy link

I got it to work by declaring it as a const

export const UserReferenceResolver = async (ref: Pick<User, 'Id'>, ctx: any, info: any): Promise<User> => {
  const fields = info.fieldNodes[0].selectionSet.selections[0].selectionSet.selections.map(
    (field: { name: { value: any } }) => field.name.value,
  );
  const option = { filters: { Id: ref.Id } };
  const user: User[] = await userRepo.getMany(ctx, fields, option as any);
  return user[0];
};

Then when building the federated schema, i just add this to the schema:

const schema = await buildFederatedSchema(
    {
      emitSchemaFile: true,
      nullableByDefault: true,
      resolvers,
      validate: false,
    },
    {
      User: { __resolveReference: UserReferenceResolver },
      Account: { __resolveReference: AccountReferenceResolver },
    },
  );

I guess this is the only workaround until @FederationKey is implemented?

@biancazzurri
Copy link

I got it to work by declaring it as a const

export const UserReferenceResolver = async (ref: Pick<User, 'Id'>, ctx: any, info: any): Promise<User> => {
  const fields = info.fieldNodes[0].selectionSet.selections[0].selectionSet.selections.map(
    (field: { name: { value: any } }) => field.name.value,
  );
  const option = { filters: { Id: ref.Id } };
  const user: User[] = await userRepo.getMany(ctx, fields, option as any);
  return user[0];
};

Then when building the federated schema, i just add this to the schema:

const schema = await buildFederatedSchema(
    {
      emitSchemaFile: true,
      nullableByDefault: true,
      resolvers,
      validate: false,
    },
    {
      User: { __resolveReference: UserReferenceResolver },
      Account: { __resolveReference: AccountReferenceResolver },
    },
  );

I guess this is the only workaround until @FederationKey is implemented?

this works for me too! thanks

How hard is it to create syntactic sugar for this via @ObjectType directive, and make it receive __resolveReference function?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Community 👨‍👧 Something initiated by a community Discussion 💬 Brainstorm about the idea Enhancement 🆕 New feature or request
Projects
None yet
Development

No branches or pull requests