Skip to content

feat(federation): Use extensions instead of custom federation objects #4313

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

Merged
merged 5 commits into from
Jul 1, 2020

Conversation

trevor-scheer
Copy link
Contributor

@trevor-scheer trevor-scheer commented Jun 26, 2020

Composition in its current form modifies the graphql-js types in order to accommodate some additional metadata required during composition, validation, query planning, and execution. This is no longer required due to the addition of extensions, which is intended for exactly this use case.

This PR transitions our usages of these custom metadata objects into the extensions object where they belong.

This work should unblock future work on #4310 and allow for some recommended cleanup.

Composition in its current form modifies the graphql-js types in
order to accommodate some additional metadata required during
composition, validation, query planning, and execution. This is
no longer required due to the addition of 'extensions', which
this is intended to be used exactly for.

This commit transitions our usages of these custom metadata
objects into the extensions object where they belong.
@trevor-scheer trevor-scheer force-pushed the trevor/federation-use-extensions branch from 41655a5 to 25a7c15 Compare June 26, 2020 01:03
Copy link
Member

@abernix abernix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM w/ a couple notes within. Very exciting to flush out this older technique in favor of the functionality offered with graphql/graphql-js#2097! Thanks for your time working on this!

Comment on lines -49 to -96
declare module 'graphql/type/definition' {
interface GraphQLObjectType {
federation?: FederationType;
}

interface GraphQLEnumType {
federation?: FederationType;
}

interface GraphQLScalarType {
federation?: FederationType;
}

interface GraphQLInterfaceType {
federation?: FederationType;
}

interface GraphQLUnionType {
federation?: FederationType;
}

interface GraphQLInputObjectType {
federation?: FederationType;
}

interface GraphQLEnumValue {
federation?: FederationType;
}

interface GraphQLInputField {
federation?: FederationField;
}

interface GraphQLField<TSource, TContext> {
federation?: FederationField;
}
}

declare module 'graphql/type/directives' {
interface GraphQLDirective {
federation?: {
directiveDefinitions: {
[serviceName: string]: DirectiveDefinitionNode;
};
};
}
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

Comment on lines +426 to 429
namedType.extensions = {
...namedType.extensions,
federation: federationMetadata,
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the event that the extensions property/object already exists (does it always, in newer graphql versions?), I can imagine that it might be beneficial to avoid creating new extensions objects and instead maintain referential equality to the existing property. For example, this could be relevant if we're federating existing schemas.

Might we consider merely adding the federation property to extensions? If it's indeed undefined until an implementor tries writing to it, we could default to creating it in that case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: we spoke offline about this and decided we'd move forward with this solution until it becomes a problem.

Sadly we can't mutate the extensions object itself as it's Readonly, so instead it must be recreated regardless of whether or not it already exists (if we're respecting the types, anyway).

For posterity, @IvanGoncharov mentioned that we can use transformSchema here. This may be the way forward for types, but it leaves us with the same solution for a field's extensions below. I'm also hesitant to use transformSchema here since it seems to require a rework of the existing approach. Rather than modify the types one at a time, I think we would need to collect a map of types and their new extensions during the for loop, followed by a transformSchema once we've mapped over the types.

Comment on lines +453 to +455
field.extensions = {
...field.extensions,
federation: fieldFederationMetadata
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same concern for creating new extensions objects rather than re-cycling the old ones, when present.

Comment on lines +478 to +480
field.extensions = {
...field.extensions,
federation: fieldFederationMetadata,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same!

Comment on lines +500 to +502
field.extensions = {
...field.extensions,
federation: fieldFederationMetadata,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same!

Comment on lines +525 to +527
namedType.extensions = {
...namedType.extensions,
federation: typeFederationMetadata,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same!

Comment on lines +541 to +544
directive.extensions = {
...directive.extensions,
federation: directiveFederationMetadata,
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👻

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@IvanGoncharov jut pointing out one more place we didn't address in our discussion: directive extensions!

@trevor-scheer trevor-scheer merged commit 39e678c into main Jul 1, 2020
@trevor-scheer trevor-scheer deleted the trevor/federation-use-extensions branch July 1, 2020 04:35
abernix pushed a commit to apollographql/federation that referenced this pull request Sep 4, 2020
…ts (apollographql/apollo-server#4313)

Note: BREAKING CHANGE
In order to guarantee the safe usage of extensions, we must now
require `graphql@>14.5.0`. As such, we've narrowed the range in
federation and gateway's `peerDependencies`.

Composition in its current form modifies the graphql-js types in
order to accommodate some additional metadata required during
composition, validation, query planning, and execution. This is
no longer required due to the addition of 'extensions', which
this is intended to be used exactly for.

This commit transitions our usages of these custom metadata
objects into the extensions object where they belong.
Apollo-Orig-Commit-AS: apollographql/apollo-server@39e678c
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 16, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants