-
Notifications
You must be signed in to change notification settings - Fork 254
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
chore(gateway): Simplify startup code paths #440
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow, this is a lot more clear than the old version.
gateway-js/src/index.ts
Outdated
@@ -727,13 +793,40 @@ export class ApolloGateway implements GraphQLService { | |||
}); | |||
} | |||
|
|||
const canUseManagedConfig = | |||
this.apolloConfig?.graphId && this.apolloConfig?.keyHash; | |||
if (!canUseManagedConfig) { | |||
throw new Error( | |||
'When `serviceList` is not set, an Apollo configuration must be provided. See https://www.apollographql.com/docs/apollo-server/federation/managed-federation/ for more information.', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error isn't quite right, since there are other approaches like csdl
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also added a test for this: 238fbec
Open to suggestions on rewording this, this one's kinda weird and, as demonstrated here, not the most obvious to keep up to date.
this.maybeWarnOnConflictingConfig(); | ||
} | ||
|
||
await this.updateComposition(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it would be helpful to rename updateComposition and updateServiceDefinitions to contain "dynamic", to be more clear they are only part of the dynamic case? On the other hand maybe this is implicit in the concept of updating.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm open to this but the names will get to be on the "mouthful" side. I'm open to suggestions if you can think of something nice, otherwise will leave as is for now.
I do think there is something to be said for "any given field is either always or never assigned synchronously by the constructor" as kind of an abstract principle that this is going against. But I think practically speaking it's nice to be able to have things synchronously exist when possible. |
Static init now happens under `load()` in a similar way to dynamic init. Additional cleanup / DRYing around these fns.
@glasser I think this was a great comment:
Addressed via 525df37. Let me know if you meant something else, but this was my takeaway and I'm pretty happy with the result. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
basically good, a few tiny suggestions?
gateway-js/src/index.ts
Outdated
({ schema, composedSdl } = this.createSchema(schemaConstructionOpts)); | ||
} catch (e) { | ||
throw Error( | ||
"A valid schema couldn't be composed. The following errors were found:\n" + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this error appropriate in the CSDL case? It wasn't there before this commit and the wording is a bit off for it.
(Though I do wonder why we need this try/catch anyway rather than have createSchema throw a useful error in the first place? What particular error is this trying to annotate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're correct, this.createSchema
flat out won't throw in the CSDL case so I can go back to splitting the cases via two if
s.
This message precedes a list of graphql errors. We could just let that bubble up and not annotate it at all, or add this annotation over there instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I guess I'm wondering if adding it more tightly would help. It's an error about "composing", it could be thrown in the compose function or right when that is called?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a nice change and additionally gives us better control over how the list of composition errors is displayed (GraphQLSchemaValidationError
forced multiple new lines between each error in the printing).
d66d683
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool. I don't think I understand the implications of switching from GraphQLSchemaValidationError
to just a plain Error
(eg nothing checks error types when catching, right?) but trust that you do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this is a safe change (esp. since we were already just catching it and rethrowing as a plain Error
).
There may have been some things to be careful of if it was public surface area / in use in other places, but not the case here.
const mode = isManagedConfig(this.config) ? 'managed' : 'unmanaged'; | ||
// Handles initial assignment of `this.schema`, `this.queryPlannerPointer` | ||
isStaticConfig(this.config) | ||
? this.loadStatic(this.config) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a particular reason that loadStatic takes config as an argument and loadDynamic reads it from this
? I mean it's not bad for them to have different interfaces, but if it's actually important that they differ I don't see why.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
loadDynamic
no longer reads from config at all, which is why the arg went away.
I prefer loadStatic
to accept it as an argument since we can refine the type of config to StaticConfig
on its way in, rather than do another isStaticConfig(this.config)
once inside the function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see — loadDynamic calls functions which read from this.config but doesn't do so directly.
Actually, thinking about this makes me wonder if Experimental_UpdateServiceDefinitions
should be changed to take a DynamicConfig instead of a GatewayConfig?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
565f217
to
d318891
Compare
gateway-js/src/index.ts
Outdated
@@ -781,8 +781,7 @@ export class ApolloGateway implements GraphQLService { | |||
graphId: this.apolloConfig!.graphId!, | |||
apiKeyHash: this.apolloConfig!.keyHash!, | |||
graphVariant: this.apolloConfig!.graphVariant, | |||
federationVersion: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's cool!
gateway-js/src/index.ts
Outdated
({ schema, composedSdl } = this.createSchema(schemaConstructionOpts)); | ||
} catch (e) { | ||
throw Error( | ||
"A valid schema couldn't be composed. The following errors were found:\n" + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool. I don't think I understand the implications of switching from GraphQLSchemaValidationError
to just a plain Error
(eg nothing checks error types when catching, right?) but trust that you do.
The point of apollo-server-caching is to provide an abstraction over multiple cache backends. Gateway is not using that abstraction; it's just using one particular implementation, which is a wrapper around an old version of lru-cache. As part of apollographql/apollo-server#6057 and apollographql/apollo-server#6719 we want to remove dependencies on Apollo Server from Apollo Gateway. Technically we don't really need to remove this dependency (since apollo-server-caching doesn't depend on anything else in AS) but apollo-server-caching won't be updated any more (in fact, even in AS3 it has already been replaced by `@apollo/utils.keyvaluecache`), so let's do it. While we're at it, we make a few other improvements: - Ever since #440, the queryPlanStore field is always set, so we can remove some conditionals around it. - Instead of using the old lru-cache@6 wrapped by the apollo-server-caching package, we use the newer lru-cache@7 (which improves the algorithm internally and changes the names of methods a bit). - The get and set methods on InMemoryLRUCache were only async because they implement the abstract KeyValueCache interface: the implementations didn't actually do anything async. So we no longer need to await them or include a giant comment about how we're not awaiting them.
The point of apollo-server-caching is to provide an abstraction over multiple cache backends. Gateway is not using that abstraction; it's just using one particular implementation, which is a wrapper around an old version of lru-cache. As part of apollographql/apollo-server#6057 and apollographql/apollo-server#6719 we want to remove dependencies on Apollo Server from Apollo Gateway. Technically we don't really need to remove this dependency (since apollo-server-caching doesn't depend on anything else in AS) but apollo-server-caching won't be updated any more (in fact, even in AS3 it has already been replaced by `@apollo/utils.keyvaluecache`), so let's do it. While we're at it, we make a few other improvements: - Ever since #440, the queryPlanStore field is always set, so we can remove some conditionals around it. - Instead of using the old lru-cache@6 wrapped by the apollo-server-caching package, we use the newer lru-cache@7 (which improves the algorithm internally and changes the names of methods a bit). - The get and set methods on InMemoryLRUCache were only async because they implement the abstract KeyValueCache interface: the implementations didn't actually do anything async. So we no longer need to await them or include a giant comment about how we're not awaiting them.
The point of apollo-server-caching is to provide an abstraction over multiple cache backends. Gateway is not using that abstraction; it's just using one particular implementation, which is a wrapper around an old version of lru-cache. As part of apollographql/apollo-server#6057 and apollographql/apollo-server#6719 we want to remove dependencies on Apollo Server from Apollo Gateway. Technically we don't really need to remove this dependency (since apollo-server-caching doesn't depend on anything else in AS) but apollo-server-caching won't be updated any more (in fact, even in AS3 it has already been replaced by `@apollo/utils.keyvaluecache`), so let's do it. While we're at it, we make a few other improvements: - Ever since #440, the queryPlanStore field is always set, so we can remove some conditionals around it. - Instead of using the old lru-cache@6 wrapped by the apollo-server-caching package, we use the newer lru-cache@7 (which improves the algorithm internally and changes the names of methods a bit). - The get and set methods on InMemoryLRUCache were only async because they implement the abstract KeyValueCache interface: the implementations didn't actually do anything async. So we no longer need to await them or include a giant comment about how we're not awaiting them.
* gateway: remove dependency on apollo-server-caching (#2029) The point of apollo-server-caching is to provide an abstraction over multiple cache backends. Gateway is not using that abstraction; it's just using one particular implementation, which is a wrapper around an old version of lru-cache. As part of apollographql/apollo-server#6057 and apollographql/apollo-server#6719 we want to remove dependencies on Apollo Server from Apollo Gateway. Technically we don't really need to remove this dependency (since apollo-server-caching doesn't depend on anything else in AS) but apollo-server-caching won't be updated any more (in fact, even in AS3 it has already been replaced by `@apollo/utils.keyvaluecache`), so let's do it. While we're at it, we make a few other improvements: - Ever since #440, the queryPlanStore field is always set, so we can remove some conditionals around it. - Instead of using the old lru-cache@6 wrapped by the apollo-server-caching package, we use the newer lru-cache@7 (which improves the algorithm internally and changes the names of methods a bit). - The get and set methods on InMemoryLRUCache were only async because they implement the abstract KeyValueCache interface: the implementations didn't actually do anything async. So we no longer need to await them or include a giant comment about how we're not awaiting them. * gateway RemoteGraphQLDataSource: throw GraphQLError, not ApolloError (#2028) This is part of apollographql/apollo-server#6057 (which is itself part of apollographql/apollo-server#6719). We are trying to break the dependency of Gateway on Server so that (among other things) it is easier to have a single version of Gateway that works with both the current AS3 and the upcoming AS4. In AS4, we are removing the ApolloError class and its subclasses. Instead, we will just use GraphQLError directly. See: https://www.apollographql.com/docs/apollo-server/v4/migration#apolloerror https://www.apollographql.com/docs/apollo-server/v4/migration#built-in-error-classes apollographql/apollo-server#6355 apollographql/apollo-server#6705 This commit changes RemoteGraphQLDataSource to throw GraphQLError instead of ApolloError. The `code` extension will still be the same. (The `name` field of the thrown Error will no longer be eg `AuthenticationError`, though; this does not affect the error as serialized in GraphQL.) This is technically slightly backwards-incompatible (eg, the method errorFromResponse is public and now returns GraphQLError instead of the tighter ApolloError) but this doesn't seem likely to affect many users. We can adjust based on feedback if necessary. * Adjust #2028 for [email protected] compatibility
In my attempts to implement CSDL fetching for managed gateway configurations, I found the complexity of our startup code to really inhibit my progress (and in turn, I'll need to add a bit more complexity for what I'm trying to accomplish).
After thinking through what is actually happening during startup (and wanting to simplify the code paths specifically
loadServiceDefinitions
), I was able to tease out the idea of static vs. dynamic configs and isolate those code paths a bit. Static === (local || csdl) configs where the gateway never updates.Previously, we had
loadServiceDefinitions
overloaded to handle every type of config, but really it doesn't need to concern itself with the static cases. Those cases, I found, could actually be handled synchronously within the constructor!The effect of these changes can be seen in the test updates that I had to make - the gateway was actually going through a full async update cycle even in the cases where that was totally unnecessary.