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

Add federation support #455

Merged
merged 33 commits into from
Feb 23, 2020
Merged

Conversation

rickdgeerling
Copy link
Contributor

@rickdgeerling rickdgeerling commented Oct 29, 2019

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

[ ] Bugfix
[X] Feature
[ ] Code style update (formatting, local variables)
[ ] Refactoring (no functional changes, no api changes)
[X] Build related changes
[ ] CI related changes
[ ] Other... Please describe:

What is the current behavior?

Issue Number:
#299
#288

What is the new behavior?

Support for Apollo Federation

Does this PR introduce a breaking change?

[ ] Yes
[X] No

Other information

This is a follow-up PR to #301 by @marcus-sa . Many thanks to his original implementation, I want to use it and had some spare time, so I implemented the unit tests and will start the documentation tomorrow. Don't know the correct etiquette for this sort of situation, opening up a new PR seemed easiest.

@rickdgeerling
Copy link
Contributor Author

rickdgeerling commented Oct 29, 2019

I could still use some help for one more test case I'd like to cover. Marcus provided a string token to register a buildService as a nest injectable, which will be passed to Apollo Gateway. However, as I'm relatively new to Nest, I couldn't figure out how to fill this injectable 🤷 Could really use some suggestions here :)

// graphql-gateway.module.ts
@Module({})
export class GraphQLGatewayModule implements OnModuleInit {
  private apolloServer: ApolloServerBase;

  constructor(
    @Optional()
    private readonly httpAdapterHost: HttpAdapterHost,

    @Optional()
    @Inject(GRAPHQL_GATEWAY_BUILD_SERVICE) // <-- This thing here
    private readonly buildService: GatewayBuildService,

    @Inject(GRAPHQL_GATEWAY_MODULE_OPTIONS)
    private readonly options: GatewayModuleOptions,
  ) {}

I figured something like this would work, but sadly no luck

class BuildService extends RemoteGraphQLDataSource {
  willSendRequest() {
    console.log('RECEIVED!!');
  }
}

@Module({
  imports: [
    GraphQLGatewayModule.forRoot(/* config */),
  ],
  providers: [{
    provide: GRAPHQL_GATEWAY_BUILD_SERVICE,
    useClass: BuildService,
  }],
})
export class AppModule {}

edit: @marcus-sa you'll probably know best how to fix this one :)

@marcus-sa
Copy link
Contributor

marcus-sa commented Oct 29, 2019

Btw you should upgrade the Apollo dependencies as there's a breaking change in the gateway class options.

The way I initially wanted to implement the BuildService was an injectable that works like an async options factory basically (hopefully this gives you a clue)

HINT: The reason why the provider is unavailable in the module context, is because:

  1. The imported module is in a different scope (hierarchy)
  2. Imported modules gets initialized before parents providers

@rickdgeerling
Copy link
Contributor Author

I rebased your branch on master where Apollo is already updated but I forgot to check the changelog. Will do that tomorrow, thanks for the heads up!

Figured if was something like that, tried injecting it into a dynamic module using the forRootAsync but couldn't get it working. Will give it another shot tomorrow.

@rickdgeerling
Copy link
Contributor Author

  • Added test for custom buildservice
  • Updated @apollo/{gateway,federation} dependencies
  • Started docs on tuxmachine/docs.nestjs.com

@kamilmysliwiec Ready for a review 👍

@rickdgeerling
Copy link
Contributor Author

Docs added nestjs/docs.nestjs.com#802

@yeuem1vannam
Copy link

Does this PR also provide a way to share the context across services?
References: https://www.apollographql.com/docs/apollo-server/federation/implementing/#sharing-context-across-services

@rickdgeerling
Copy link
Contributor Author

Yes it does, but maybe the docs need to be clearer om that.

https://deploy-preview-802--docs-nestjs.netlify.com/graphql/federation#build-service

@rickdgeerling
Copy link
Contributor Author

If there's anything more that I can do regarding this PR, please let me know :) happy to spend some more time on it.

@floriantz
Copy link

@tuxmachine Have you tested Custom scalars and Enum internal values features from ApolloServer with this ?

I tried federation using @marcus-sa branch you started working from I believe and passing scalars/enums in the resolvers argument when importing GraphQLFederationModule did not work. For example with the GraphQLJSON Scalar from graphql-type-json package:

GraphQLFederationModule.forRoot({
      typePaths: [...GQLTypesPath, './**/*.type.gql'],
      resolvers: { JSON: GraphQLJSON },
      context,
    })

It seems the JSON scalar is basically ignored, and any kind of input can go through.

@marcus-sa
Copy link
Contributor

marcus-sa commented Nov 4, 2019

@ZFLloyd nope, I never got to that point.

@rickdgeerling
Copy link
Contributor Author

@ZFLloyd Thanks for highlighting a missing piece. My real-world experience with both nest and graphql are limited, so the feedback is really valuable. I'll see what I can do this week.

@yeuem1vannam
Copy link

@tuxmachine @marcus-sa
Thank you so much for the implementation of this feature, this is super useful for me right now.

However, when working with Apollo Graph Manager, this line fire error

// lib/graphql-gateway.module.ts:L107
const { schema, executor } = await gateway.load();
Error Log
[WARN] Tue Nov 05 2019 14:35:44 GMT+0000 (Coordinated Universal Time) apollo-gateway: Error checking for schema updates. Falling back to existing schema. Error: When `serviceList` is not set, an Apollo Engine configuration must be provided. See https://www.apollographql.com/docs/apollo-server/federation/managed-federation/ for more information.
    at ApolloGateway.<anonymous> (/node_modules/@apollo/gateway/dist/index.js:271:23)
    at Generator.next (<anonymous>)
    at /node_modules/@apollo/gateway/dist/index.js:8:71
    at new Promise (<anonymous>)
    at __awaiter (/node_modules/@apollo/gateway/dist/index.js:4:12)
    at ApolloGateway.loadServiceDefinitions (/node_modules/@apollo/gateway/dist/index.js:260:16)
    at ApolloGateway.<anonymous> (/node_modules/@apollo/gateway/dist/index.js:162:37)
    at Generator.next (<anonymous>)
    at /node_modules/@apollo/gateway/dist/index.js:8:71
    at new Promise (<anonymous>)
(node:1515) UnhandledPromiseRejectionWarning: Error: Apollo Server requires either an existing schema, modules or typeDefs
    at ApolloServer.initSchema (/node_modules/apollo-server-core/dist/ApolloServer.js:240:23)
    at new ApolloServerBase (/node_modules/apollo-server-core/dist/ApolloServer.js:202:30)
    at new ApolloServer (/node_modules/apollo-server-express/dist/ApolloServer.js:59:9)
    at GraphQLGatewayModule.registerExpress (/workspace/packages/nestjs-graphql-pull-455/dist/graphql-gateway.module.js:99:30)
    at GraphQLGatewayModule.registerGqlServer (/workspace/packages/nestjs-graphql-pull-455/dist/graphql-gateway.module.js:86:18)
    at GraphQLGatewayModule.<anonymous> (/workspace/packages/nestjs-graphql-pull-455/dist/graphql-gateway.module.js:76:18)
    at Generator.next (<anonymous>)
    at fulfilled (/node_modules/tslib/tslib.js:107:62)
    at process._tickCallback (internal/process/next_tick.js:68:7)
    at Function.Module.runMain (internal/modules/cjs/loader.js:834:11)
(node:1515) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)
(node:1515) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

so I changed the code as below to make it works.

// lib/graphql-gateway.module.ts:L102-L108
const gateway = new ApolloGateway({
  ...gatewayOpts,
  buildService,
});

// const { schema, executor } = await gateway.load(); // Skip the load at this point.
this.registerGqlServer({ ...serverOpts, gateway, subscriptions: false });

By doing so, both the production (use Apollo Graph Manager) and development (use serviceList as usual) work as expected.

References:

@rickdgeerling
Copy link
Contributor Author

@yeuem1vannam Thanks for testing with Graph Manager! Your fix works with the existing tests so I added it to the PR.

@rickdgeerling
Copy link
Contributor Author

@ZFLloyd I believe I've fixed it. Could you verify?

@renepardon
Copy link

One question left: will this implementation work with the type-graphql package to support the code first development?

@marcus-sa
Copy link
Contributor

marcus-sa commented Nov 6, 2019

@renepardon nope, because type-graphql does not support custom directives.

@renepardon
Copy link

@marcus-sa so in this case i should not focus on the code first approach rather then writing the schema by myself? Good to know before I start adding to much decorators to my classes :)

@renepardon
Copy link

Should we create a new issue which stays open until MichalLytek/type-graphql#77 is released?

@rickdgeerling
Copy link
Contributor Author

rickdgeerling commented Nov 6, 2019

It looks like type-graphql added support for federation directives (not all directives) just this weekend 🎉 MichalLytek/type-graphql#351 (comment)

So yeah, this PR fixes the schema-first approach. There needs to be extra work done for the code-first approach but it appears to no longer be blocking

EDIT: The support for directives is merged to master but still unreleased.

@rickdgeerling rickdgeerling changed the title Feat/federation Add federation support Nov 6, 2019
@rickdgeerling
Copy link
Contributor Author

@kamilmysliwiec I'm assuming you'd like to wait with this feature until it can support the code-first approach?

Other than that, anything else you'd like to see?

@lgabeskiria
Copy link

@kamilmysliwiec do you have any ETA for this feature to be released?

Rick Dutour Geerling and others added 4 commits January 20, 2020 12:59
@XBeg9
Copy link

XBeg9 commented Jan 20, 2020

@tuxmachine, is there anything important fixed in your last batch of commits? Thanks

@rickdgeerling
Copy link
Contributor Author

@XBeg9 Not really. Rebased on master to keep everything up to date. Two minor things:

  • Was a minor merge conflict because Marcus had added a barrel file in the utils folder, but one of the utils has been deleted in the master branch.
  • One of the apollo packages had updated and interfaces were changed... but @types/graphql has not been updated because it's deprecated. It was causing typescript to refuse to compile. Since the graphql package comes with types included, I removed the dependency.

@zackerydev
Copy link

Thanks for your work on this @tuxmachine

@kamilmysliwiec Do you have any updates on review/merge? Is there anything us invested in this issue can do to help?

@robbyemmert
Copy link

Is there anything holding this PR up?

@robbyemmert
Copy link

Is there anything holding this PR up?

If it's just conflicts on the package.json, maybe I can help?

@rickdgeerling
Copy link
Contributor Author

Is there anything holding this PR up?

Nope it's just a large PR, takes time to review and maintainers are very busy.

@kamilmysliwiec
Copy link
Member

Thank you! Published as 6.6.0 :)

@kauandotnet
Copy link

kauandotnet commented Feb 23, 2020

@tuxmachine, @marcus-sa Thanks for yours contributions.
Im excited to start working with federated schema on NestJS ecosystem (Waiting docs merge).

@kamilmysliwiec Thanks for accepting, I believe you must be a very busy person. With this, NestJS becomes even more versatile, and its built-in modules are very helpful, making developers' lives a little easier.

@rickdgeerling rickdgeerling deleted the feat/federation branch February 24, 2020 13:50
@rickdgeerling rickdgeerling restored the feat/federation branch February 24, 2020 13:50
@rickdgeerling
Copy link
Contributor Author

@kamilmysliwiec Thanks for the review and merge! Feel free to ping me if issues come up

@endurance
Copy link

How are we looking on the code-first implementation? Does it work? If not, what's the current blocker?

@Ponjimon
Copy link

@endurance I already have a service up and running on Amazon ECS using a code-first implementation! So it does semm to work! 🎉

@Dragons0458
Copy link

Dragons0458 commented Mar 31, 2020

@endurance remember to use @Directive on your models.

For more information: https://github.com/MichalLytek/type-graphql/blob/master/examples/apollo-federation/inventory/product.ts

Only use the class Product and import all decorators from @nestjs/graphql

Regards!

@kamilmysliwiec
Copy link
Member

@tuxmachine would you like to create a PR with an update to the docs (code first way)? It works exactly the same as in e2e tests you wrote (@Directive() decorator etc)

@rickdgeerling
Copy link
Contributor Author

@kamilmysliwiec Yeah no problem. I'll try to write the docs for code-first federation somewhere this month.

@kamilmysliwiec
Copy link
Member

Thank you @tuxmachine!

@joemckie
Copy link

joemckie commented May 1, 2020

@lookapanda is there any chance you could share some basic code until the docs have been written? I'm attempting to migrate to a code-first federated server, but GraphQLFederationFactory is literally throwing an error that says "Code-first approach is not supported yet", so I'm just wondering how you did it 😄

@rickdgeerling
Copy link
Contributor Author

@joemckie In rough lines:

  • You'll have to fork nest/graphql repo
  • Delete the line throwing the error
  • npm install type-graphql@beta
  • Commit and push those changes to your fork
  • In your project that wants to use schema-first: npm install joemckie/graphql

Look at the test files for some examples using code-first federation

@joemckie
Copy link

joemckie commented May 2, 2020

@tuxmachine thanks! Will give that a try 😄

@kamilmysliwiec
Copy link
Member

@tuxmachine pinging in you here hoping you'll get a notification 😅 what's the best way to reach out to you? or better, can you message me on twitter/linkedin/via email ([email protected])?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.