Skip to content

Save and reuse WebClient response #328

Closed
@ciscoo

Description

@ciscoo

My company has various of services that provides customer data. I'm trying to build POC to showcase what is possible to maybe solve our own internal issues.

There is no single "view" of the data and instead we must retrieve the data we need from the various services. Using Reactor, I thought I would be able to save the response from a request, and reuse the response if a different field is queried from that same response.

For example, given a todo, let's say the description and completed come from the same API. I want to be able to save the response in the Reactor context and reuse if more fields are required.

@SchemaMapping(typeName = "Todo")
@Controller
public class TodoController {

    private static final ParameterizedTypeReference<Map<String, Object>> MAP_TYPE = new ParameterizedTypeReference<>() {};

    private static final String RESPONSE_ATTR = TodoController.class.getName() + ".todoResponse";

    private final WebClient todosWebClient;

    public TodoController(WebClient todosWebClient) {
        this.todosWebClient = todosWebClient;
    }

    @SchemaMapping(field = "title")
    public Mono<String> resolveTitle(Todo todo) {
        return Mono.deferContextual(contextView -> {
            Map<String, Object> cached = contextView.getOrDefault(RESPONSE_ATTR, null);
            if (cached != null) {
                return Mono.just(cached.get("title").toString());
            }
            return this.todosWebClient.get().uri("/todos/{id}", todo.getId())
                    .retrieve()
                    .bodyToMono(MAP_TYPE)
                    .flatMap(res -> Mono.just(res).contextWrite(context -> context.put(RESPONSE_ATTR, res)))
                    .flatMap(res -> Mono.just(res.get("title").toString()));
        });
    }

    @SchemaMapping(field = "completed")
    public Mono<Boolean> resolveCompleted(Todo todo) {
        return Mono.deferContextual(contextView -> {
            Map<String, Object> cached = contextView.getOrDefault(RESPONSE_ATTR, null);
            if (cached != null) {
                return Mono.just(Boolean.parseBoolean(cached.get("completed").toString()));
            }
            return this.todosWebClient.get().uri("/todos/{id}", todo.getId())
                    .retrieve()
                    .bodyToMono(MAP_TYPE)
                    .flatMap(res -> Mono.just(res).contextWrite(context -> context.put(RESPONSE_ATTR, res)))
                    .flatMap(res -> Mono.just(Boolean.parseBoolean(res.get("completed").toString())));
        });
    }

}

However, seems this approach does not work since the logs show (2) requests being sent:

2022-03-14 14:02:36.473 DEBUG 28960 --- [ttp@13e9f2e2-76] o.s.w.r.f.client.ExchangeFunctions       : [7f28f96f] HTTP GET https://jsonplaceholder.typicode.com/todos/2
2022-03-14 14:02:36.627 DEBUG 28960 --- [ttp@13e9f2e2-76] o.s.w.r.f.client.ExchangeFunctions       : [4301696d] HTTP GET https://jsonplaceholder.typicode.com/todos/2
2022-03-14 14:02:36.948 DEBUG 28960 --- [or-http-epoll-3] o.s.w.r.f.client.ExchangeFunctions       : [4301696d] [500c0d95-1] Response 200 OK
2022-03-14 14:02:36.948 DEBUG 28960 --- [or-http-epoll-2] o.s.w.r.f.client.ExchangeFunctions       : [7f28f96f] [d12daeed-1] Response 200 OK

I have uploaded a sample project of the above approach: https://github.com/ciscoo/cache-graphql


Side note, I'm trying to build a single view into our data. For example, the todoDetails comes from a service which I want to "unwrap" if that makes sense.

{
    "id": 1,
    "todoDetails": {
        "id": 1,
        "completed": false
    }
}

to

{
    "id": 1,
    "completed": false
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions