Sometimes you come to situation you need caching your graphql api. High load services, repeatable data, huge size of response are prerequisite of your needs.
I use DGS framework, it is a great open-source tool for writing graphql server api supported by Netflix.
Unfortunately, it does not support caching of full response. There is an issue of that and a pull-request but it is not merged.
So caching should be solved in different way. And it is possible with extending DGS implementation.
The interface DgsReactiveQueryExecutor allows to get full response of request, we have a contract
Mono<ExecutionResult> execute(@Language("GraphQL") String query,
Map<String, Object> variables,
Map<String, Object> extensions,
HttpHeaders headers,
String operationName,
ServerRequest serverRequest);
So we need to write our own class with cache functionality. It will be like decorator pattern and such way will allow us add new behaviour to the origin class. You can use any cache tool, I used caffeine.
class CacheableDefaultDgsReactiveQueryExecutor(
private val defaultDgsReactiveQueryExecutor: DgsReactiveQueryExecutor,
private val graphqlExecutionResultCache: AsyncCache<GraphQlRequestParamsCacheKey, ExecutionResult>,
) : DgsReactiveQueryExecutor {
override fun execute(
query: String?,
variables: MutableMap<String, Any>?,
extensions: MutableMap<String, Any>?,
headers: HttpHeaders?,
operationName: String?,
serverHttpRequest: ServerRequest?
): Mono<ExecutionResult> {
val params = GraphQlRequestParamsCacheKey(query, variables)
return graphqlExecutionResultCache.get(params) { _: GraphQlRequestParamsCacheKey ->
defaultDgsReactiveQueryExecutor.execute(
query,
variables,
extensions,
headers,
operationName,
serverHttpRequest
).block()
}.toMono()
}
// other methods just pass invocation to defaultDgsReactiveQueryExecutor
Important part is GraphQlRequestParamsCacheKey
, you should choose a key for cache, I put query and variables but it can not be enough in your case. So you should consider putting all other arguments of the method. Be carefully with size of the cache in this case.
As we see DgsWebFluxAutoConfiguration does not have condition on bean dgsReactiveQueryExecutor
, it means bean will be created always by configuration, thus we need to create our own bean with primary annotation.
@Primary
@Bean
fun cacheableDefaultDgsReactiveQueryExecutor(
defaultDgsReactiveQueryExecutor: DgsReactiveQueryExecutor,
graphqlExecutionResultCache: AsyncCache<GraphQlRequestParamsCacheKey, ExecutionResult>,
): DgsReactiveQueryExecutor =
CacheableDefaultDgsReactiveQueryExecutor(
defaultDgsReactiveQueryExecutor = defaultDgsReactiveQueryExecutor,
graphqlExecutionResultCache = graphqlExecutionResultCache,
)
That's it. It has really helped me for improving performance of my graphql api. And I hope such approach will help you too.
Top comments (0)