DEV Community

Cover image for Caching DGS framework
Andrei Chugunov
Andrei Chugunov

Posted on

Caching DGS framework

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);
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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,
        )

Enter fullscreen mode Exit fullscreen mode

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)