so I want to implement caching. I’m using version 11 hc. Now every time pagination happens there’s requests sent to the database. I want to implement it so the first time a request is made, all the data is retrieved and cached and subsequent requests(going to the next page or previous page) are retrieved from cache. Any ideas on how I can get started?
4
1 Answer
Just have implemented this solution, seems like it works well to me. Hope it is still relevant.
So the middleware class itself:
internal sealed class CachingMiddleware
{
private readonly RequestDelegate _next;
public CachingMiddleware(
RequestDelegate next)
{
_next = next ??
throw new ArgumentNullException(nameof(next));
}
public async ValueTask InvokeAsync(IRequestContext context, [Service] ICacheService cache)
{
var key = context.Document.ToString();
var res = await cache.GetDataAsync<Dictionary<string, object?>?>(key);
var isCached = res is not null;
if (isCached)
{
context.Result = new QueryResult(res);
}
await _next(context).ConfigureAwait(false);
if (!isCached)
{
var toCache = ((QueryResult)context.Result).Data;
await cache.SetDataAsync(key, toCache, DateTimeOffset.UtcNow.AddSeconds(30));
}
}
}
Note that cache service is just a wrapper over default redis IDataBase interface, so you can implement it however you want.
Then in your program.cs or wherever your DI logic is:
services.AddGraphQLServer()
.AddQueryType<ArtistsQL>()
.AddProjections()
.AddSorting()
.AddFiltering()
.AddAuthorization()
//request pipeline starts
.UseInstrumentation()
.UseExceptions()
.UseTimeout()
.UseDocumentCache()
.UseDocumentParser()
.UseDocumentValidation()
.UseRequest<CachingMiddleware>()
.UseOperationCache()
.UseOperationComplexityAnalyzer()
.UseOperationResolver()
.UseOperationVariableCoercion()
.UseOperationExecution();
Every method that starts with "Use" except UseRequest is from default request pipeline in hot chocolate.
Note that the methods order is important, think twice before change something. I’m also not sure about the place of CachingMiddleware, so if you think that it should be somewhere else, let me know in the comments.
Here’s my example query:
public class ArtistsQL
{
[UsePaging(IncludeTotalCount = true)]
[UseProjection]
[UseFiltering]
[UseSorting]
// [Authorize(Role.RoleNames.Default)]
public async Task<IQueryable<Artist>> GetArtists([Service(ServiceKind.Resolver)] IApplicationDbContext context)
{
return context.Artists;
}
}
Check this out if want to know more:
Guide about hot chocolate middlewares
we are having same requirement in our product to cache output of API calls to avoid database calls. If you have found the solution for this can you please share.
Feb 10 at 4:54
create a middleware class which will have your cache logic. inject an instance of your memory cache/redis into the class. register your middleware just before pagination. paging middleware works with deffered execution and is capable to work on both data or a query. if it is a query, it will do an offset, creating a new sql to send to db. if it is data,no sql to db. Paging will operate on the data.
Feb 11 at 6:04
So if the data is not in the cache then send the request to db by doing a toList() on your query variable so its execution is no longer deffered and add that to the cache so paging only ever operates on data.
Feb 11 at 6:04
I was using v10 chillicream.com/docs/hotchocolate/v10/execution-engine. your key for the cache can be the query string and manipulated according to your specs.
Feb 11 at 6:08