Caching DGS framework
Tags: graphql, caching, springboot, kotlin
Published: 2023-02-01 04:08:22

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](https://github.com/Netflix/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](https://github.com/Netflix/dgs-framework/issues/929) 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](https://github.com/Netflix/dgs-framework/blob/618cd433c2431306c780f09f7bbe4fd3f4320c30/graphql-dgs-reactive/src/main/java/com/netflix/graphql/dgs/reactive/DgsReactiveQueryExecutor.java) allows to get full response of request, we have a contract 

```java
    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](https://en.wikipedia.org/wiki/Decorator_pattern) and such way will allow us add new behaviour to the origin class. You can use any cache tool, I used caffeine.

```kotlin
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](https://github.com/Netflix/dgs-framework/blob/b6481e702857ce7fab62ee3a6010e4735f080545/graphql-dgs-spring-webflux-autoconfigure/src/main/kotlin/com/netflix/graphql/dgs/webflux/autoconfiguration/DgsWebFluxAutoConfiguration.kt) 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.

```kotlin
    @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.