graphql-platform icon indicating copy to clipboard operation
graphql-platform copied to clipboard

Adds dynamic query cache-control

Open tobias-tengler opened this issue 2 years ago • 16 comments

Allow to annotate the GraphQL schema with caching hints and add middleware that dynamically computes the cache expiration time on a per query basis. In this first iteration these cache hints are used to set the HTTP Cache-Control header, but I've also did some preparation work for custom user-space cache implementation for example using Redis. However there were some open questions around short-circuiting and cache key computation that were stalling the original PR, so I decided to leave them out.

Closes #2089

Examples

The examples below only show how to apply the @cacheControl directive on object type fields, but the directive can also be applied on object types, interfaces and union types. If a field returns a type that specifies a @cacheControl directive, the field will use the cache information from the type, unless the field itself specifies a @cacheControl directive.

Annotation-based

services.AddGraphQLServer()
    .AddHttpQueryCache()
    .UseQueryCachePipeline()
    .ModifyCacheControlOptions(options =>
    {
        options.Enable = true;
        options.ApplyDefaults = true;
        options.DefaultMaxAge = 0;
    })
    .AddQueryType<Query>();

public class Query
{
    [CacheControl(100, Scope = CacheControlScope.Private)]
    public string Foo() => "Bar";
}

Code-first

services.AddGraphQLServer()
    .AddHttpQueryCache()
    .UseQueryCachePipeline()
    .ModifyCacheControlOptions(options =>
    {
        options.Enable = true;
        options.ApplyDefaults = true;
        options.DefaultMaxAge = 0;
    })
    .AddQueryType<QueryType>();

public class QueryType : ObjectType
{
    protected override void Configure(IObjectTypeDescriptor descriptor)
    {
        descriptor.Field("foo")
            .Resolve("bar")
            .CacheControl(100, scope: CacheControlScope.Private);
    }
}

Schema-first

services.AddGraphQLServer()
    .AddHttpQueryCache()
    .UseQueryCachePipeline()
    .ModifyCacheControlOptions(options =>
    {
        options.Enable = true;
        options.ApplyDefaults = true;
        options.DefaultMaxAge = 0;
    })
    .AddDocumentFromString(@"
        type Query {
            foo: String @cacheControl(maxAge: 100 scope: PRIVATE)
        }
    ");

tobias-tengler avatar Dec 12 '21 15:12 tobias-tengler

Awesome Tobias, will gobthrough it tonight

michaelstaib avatar Dec 12 '21 15:12 michaelstaib

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

No Coverage information No Coverage information
No Duplication information No Duplication information

sonarcloud[bot] avatar Dec 12 '21 16:12 sonarcloud[bot]

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

No Coverage information No Coverage information
No Duplication information No Duplication information

sonarcloud[bot] avatar Feb 23 '22 19:02 sonarcloud[bot]

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar May 04 '22 00:05 stale[bot]

@tobias-tengler Thanks. with this branch, can we try cacheControl directive. It would be great, if any usage snippets added here. Also is this targeted with v13, kindly confirm. thanks.

zf-jsk avatar May 16 '22 04:05 zf-jsk

@tobias-tengler @michaelstaib Thank you. It would be great if this can be targeted to v13

imranmomin avatar May 19 '22 18:05 imranmomin

@zf-jsk You can try out the HTTP query cache by registering it like this:

services.AddGraphQLServer()
    .AddHttpQueryCache()
    .UseQueryCachePipeline()
    // optional:
    .ModifyCacheControlOptions(options => { })

You can then use the CacheControl() method in Code-first, the [CacheControl] attribute in Annotation-based or the @cacheControl directive in Schema-first to specify rules.

I haven't worked on this in a while, so I'm no longer sure what the current limitations are, but if I remember correctly it should cover basic use cases.

If custom caches are not part of the first iteration then I think this could go into v13. I'll try to spend some time on it to get it usable with HTTP Cache-Control. :)

tobias-tengler avatar May 19 '22 18:05 tobias-tengler

@tobias-tengler thank you, lets try using it and update here. Awaiting for this feature out. :)

zf-jsk avatar May 20 '22 05:05 zf-jsk

Give me a ping if you need help @tobias-tengler

michaelstaib avatar May 21 '22 09:05 michaelstaib

@michaelstaib Could you check and see if this implementation is alright? If so, please let me know then I can add XML summaries and documentation. This version includes only the HTTP query cache, I've cut out the implementation for reading cached queries from a user-defined storage such as Redis. I think we should wait whether there is interested in having any other caches besides the HTTP cache, before starting to implement the reading portion.

tobias-tengler avatar May 21 '22 20:05 tobias-tengler

Federated cache directives is a big win. Looking forward to the outcome of this.

Arrivegrsmith avatar Aug 15 '22 20:08 Arrivegrsmith

CLA assistant check
All committers have signed the CLA.

CLAassistant avatar Aug 17 '22 14:08 CLAassistant

~~No clue why it's throwing the The FrameworkReference 'Microsoft.AspNetCore.App' was not recognized for the Caching.Http project. The csproj looks the same as the AspNetCore project, so I'm not sure where it's coming from. Maybe a .NET 7 thing, but I haven't found anything around it. Maybe one of you knows what's going on here :)~~

Resolved by using AspNetTargetFrameworks for the HotChocolate.Caching.Http project.

tobias-tengler avatar Sep 06 '22 20:09 tobias-tengler

OK ... will have a look at this

michaelstaib avatar Sep 06 '22 21:09 michaelstaib

The title of the PR, "Add HTTP query caching" is somewhat misleading. I thought that it was implementing actual response caching. But it does not appear to do that, but instead provides a way to dynamically set the cache-control header.

I find the topic of output caching, with automatic cache invalidation based on mutations, very interesting. However, it appears, we are not quite there yet.

cheema-corellian avatar Sep 08 '22 21:09 cheema-corellian

The issue name is more correct

michaelstaib avatar Sep 08 '22 22:09 michaelstaib