-
Notifications
You must be signed in to change notification settings - Fork 257
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
Cache control support with federation #870
Conversation
This is a bug in the new AS3 cache control logic. My intention is that defaultMaxAge should not be applied if you use dynamic cache control methods. I will try to get this fix into an AS 3.0.01 ASAP. Thanks for discovering it. apollographql/apollo-server#5488 |
federation-js/src/types.ts
Outdated
@@ -13,6 +13,17 @@ import { | |||
isObjectType, | |||
} from 'graphql'; | |||
import { PromiseOrValue } from 'graphql/jsutils/PromiseOrValue'; | |||
import { CacheHint, CachePolicy } from 'apollo-server-types'; | |||
|
|||
declare module 'graphql/type/definition' { |
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 probably not required any more once you rebase on #875 ?
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 still seems to be necessary even after a rebase
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.
My worry is that if you end up loading both this and apollo-server-core in your project, TypeScript will be sad to see the same declaration twice.
I'm thinking of making a chance so that the type under cacheControl
is exported from apollo-server-types, so you can do something like:
if ((info as any).cacheControl?.cacheHintFromType) {
const cacheControl: ResolveInfoCacheControl = info.cacheControl;
const cacheHint = cacheControl.cacheHintFromType(type);
federation-js/src/types.ts
Outdated
@@ -87,6 +98,15 @@ export const entitiesField: GraphQLFieldConfig<any, any> = { | |||
); | |||
} | |||
|
|||
if (info.cacheControl && info.cacheControl.cacheHint.replace) { |
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.
perhaps
if (info.cacheControl?.cacheHint?.replace) {
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.
Fixed
federation-js/src/types.ts
Outdated
info.cacheControl.cacheHintFromType(type); | ||
|
||
if (cacheHint) { | ||
info.cacheControl.cacheHint.replace(cacheHint); |
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 should be restrict, but requires the bug fix mentioned above.
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.
Fix released in 3.0.1 so it should be able to be restrict. I think it's fine for us to release this with the caveat that it doesn't work with 3.0.0.
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.
Updated to use the restrict
method with 3.0.1
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.
Let me get out another version of AS with the type I'm mentioning here.
Then the other trick is, it would be nice to have tests, but I could write them as part of the half of apollographql/apollo-server#5449 I was going to do... So maybe I should just augment this PR with that work when I get the time for it.
federation-js/src/types.ts
Outdated
const cacheHint: CacheHint | undefined = | ||
info.cacheControl.cacheHintFromType(type); | ||
|
||
if (cacheHint?.maxAge || cacheHint?.scope) { |
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 think this could just be if (cacheHint)
federation-js/src/types.ts
Outdated
@@ -13,6 +13,17 @@ import { | |||
isObjectType, | |||
} from 'graphql'; | |||
import { PromiseOrValue } from 'graphql/jsutils/PromiseOrValue'; | |||
import { CacheHint, CachePolicy } from 'apollo-server-types'; | |||
|
|||
declare module 'graphql/type/definition' { |
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.
My worry is that if you end up loading both this and apollo-server-core in your project, TypeScript will be sad to see the same declaration twice.
I'm thinking of making a chance so that the type under cacheControl
is exported from apollo-server-types, so you can do something like:
if ((info as any).cacheControl?.cacheHintFromType) {
const cacheControl: ResolveInfoCacheControl = info.cacheControl;
const cacheHint = cacheControl.cacheHintFromType(type);
This makes `info.cacheControl` available to TypeScript packages that depend on `apollo-server-types` rather than only being declared deep inside `apollo-server-core`. Additionally, it gives a name to type used for `info.cacheControl`. Intended for use cases like apollographql/federation#870 We've run into tricky issues with `declare module` before so if this ends up causing more problems than it's worth, we may revert it.
This makes `info.cacheControl` available to TypeScript packages that depend on `apollo-server-types` rather than only being declared deep inside `apollo-server-core`. Additionally, it gives a name to type used for `info.cacheControl`. Intended for use cases like apollographql/federation#870 We've run into tricky issues with `declare module` before so if this ends up causing more problems than it's worth, we may revert it.
) This makes `info.cacheControl` available to TypeScript packages that depend on `apollo-server-types` rather than only being declared deep inside `apollo-server-core`. Additionally, it gives a name to type used for `info.cacheControl`. Intended for use cases like apollographql/federation#870 We've run into tricky issues with `declare module` before so if this ends up causing more problems than it's worth, we may revert it.
@mandiwise I rebased, upgraded apollo-server-types to the version I just released, and fixed the above. Later I will add my promised half of the project and write some tests. |
9b46f6c
to
10b4a30
Compare
@mandiwise: I squashed your commits together and rewrote the PR description. This PR now implements cache control for federation, end-to-end. It requires Apollo Server 3.0.2 to work properly. It is tested end to end. I have not had a chance to seek out and update docs in this repo or apollo-server yet, and might not get to that this week. |
This is great guys @mandiwise @glasser thanks for tackle this, I've been waiting for this. Do you guys already have an ETA to release it? |
Once we've got docs and code review! |
👷 Deploy request for apollo-federation-docs pending review. 🔨 Explore the source changes: 3742084fa6cb3c11c5d4ee76a39f2bd18bf8975a |
gateway-js/src/datasources/types.ts
Outdated
* one of the strings if this operation is generated by the gateway without an | ||
* incoming request. | ||
*/ | ||
topLevelRequestContext: |
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.
@trevor-scheer Do you like this name?
Do you like this type or should we go with something more like {kind: 'health check'}|{kind: 'loading schema'}|{kind: 'incoming operation', requestContext: GraphQLRequestContext}
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.
OK, I think we definitely want the kind
sort of thing in case we want more information associated with health checks etc, so I've rebased the PR so it does that. Still curious if you like the topLevelRequestContext name :) I think it's better than incomingRequestContext because not all are based on incoming requests. Of course the fact that it doesn't always have a requestContext on it is a little odd. 🤷
The caching docs have been updated in this PR: apollographql/apollo-server#5536 |
I could possibly move the (edit: I did this) |
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.
Adding my approval for technical reasons, but this still needs review by somebody else. To be organized by @abernix.
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.
💵 🎛 🥳
@@ -15,6 +15,14 @@ beforeEach(() => { | |||
fetch.mockReset(); | |||
}); | |||
|
|||
// Right now, none of these tests care what's on topLevelRequestContext, so we |
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.
// Right now, none of these tests care what's on topLevelRequestContext, so we | |
// Right now, none of these tests care what's on incomingRequestContext, so we |
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.
Done
gateway-js/src/datasources/types.ts
Outdated
/** | ||
* Equivalent to incomingRequestContext.context (provided here for | ||
* backwards compatibility): the object created by the Apollo Server | ||
* `context` 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.
Can we add a @deprecated
annotation on this doc block?
/** | |
* Equivalent to incomingRequestContext.context (provided here for | |
* backwards compatibility): the object created by the Apollo Server | |
* `context` function. | |
*/ | |
/** | |
* Equivalent to incomingRequestContext.context (provided here for | |
* backwards compatibility): the object created by the Apollo Server | |
* `context` function. | |
* | |
* @deprecated Use `incomingRequestContext.context` 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.
Done (with a few more words)
gateway-js/src/datasources/types.ts
Outdated
context: GraphQLRequestContext<TContext>['context']; | ||
} | ||
| { | ||
kind: 'health check'; |
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 maybe just a bit off of convention. Tempting to make this an enum or enum-esque a la graphql's Kind
https://github.com/graphql/graphql-js/blob/main/src/language/kinds.ts
That being said, it's not like TS struggles at all with these string types - it'll auto-complete and type-check these kind
properties just fine as-is.
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.
OK, enum it is.
This lets the data source pay attention to any piece of the incoming request (say, its HTTP structure, or the incoming operation itself), not just the user context object. In the two cases where we use the data source without an incoming request (health checks and schema loading via introspection), pass a special string to indicate this, which lets data sources handle these operations specially. Fixes apollographql#419. Fixes apollographql#835.
Use them to restrict the overall cache policy. This behavior can be disabled by passing `honorSubgraphCacheControlHeader: false` to the RemoteGraphQLDataSource constructor (if you are setting your cache policy some other way).
Merged! Let's do a minor version bump release of federation and gateway tomorrow, and then merge apollographql/apollo-server#5536 too (adding version number references to it). |
@glasser Guys I'm trying to use the cache control in a federated schema (using the versions mentioned here) using workaround:
I'm registering the
Also the enum:
Any ideas what might I be missing? |
@vany0114 I'm not really familiar enough with the details of what you're doing to help out. For example, I'm not sure what the |
This PR allows you to use the
@cacheControl
directive with Apollo Federation. To take advantage of it, you need to be using Apollo Server 3.0.2 or newer in your gateway and all your subgraphs.Specifically, this PR:
Query._entities
resolver in subgraphs to respect@cacheControl
directives on the individual types that are in the_Entity
union. If all entity types returned in a given call have@cacheControl
directives (or useinfo.cacheControl
in their__resolveReference
), then the field's cache control policy comes from the most restrictive of these directives instead of the default ofdefaultMaxAge
(eg uncacheable by default). (If any entity type returned in a call does not specify a cache control policy, then it still defaults todefaultMaxAge
).GraphQLDataSource.process
. (Also make it possible for the method to tell if it's running a health check or fetching schemas, as has been requested.)RemoteGraphQLDataSource
, process HTTPcache-control
response headers from subgraphs and use them to restrict the operation's overall cache policy. As usual with Apollo Server cache policy, if there is nocache-control
header from a subgraph then assume the whole operation is not cacheable.(If you want to support cache control with a non-Apollo Server subgraph, just make sure it returns a proper
cache-control
HTTP header.)Fixes #356.
Fixes apollographql/apollo-server#5449.
Aided by apollographql/apollo-server#5248.