-
Notifications
You must be signed in to change notification settings - Fork 317
Save and reuse WebClient response #328
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Hello @ciscoo! I think there are several aspects to this issue. First, I don't think the Reactor context is meant to be used as a cache shared between I'm not 100% confident that I understand the use case here. BatchMapping would not work here because they don't apply to the same type? But in this sample, we're mapping fields one by one but we could fetch the entire Maybe relying on a cache and conditional HTTP requests with your client would be a better fit? |
That makes sense for Reactor context and cache stampede problems. Let me give a different example. I can certainly do the following which avoids multiple requests to the same API: input CustomerIdentifierInput {
id: String!
}
type CustomerInformation {
fristName: String!,
lastName: String!,
address: String!
}
type Customer {
info: CustomerInformation!
}
type Query {
customer(input: CustomerIdentifierInput): Customer!
}
However, what I'm trying to do is model as such: input CustomerIdentifierInput {
id: String!
}
type Customer {
fristName: String!,
lastName: String!,
address: String!
}
type Query {
customer(input: CustomerIdentifierInput): Customer!
} The My understanding of batch loading, you would be able to load or fetch a list of customers or a list of customer details/info independent of each other. But that does not work for our internal APIs since they are all structured to only provide data for the customer you request. |
Wouldn't this be just one controller method for the "customer" query? @Controller
public class CustomController {
@QueryMapping
public Customer customer(@Argument CustomerIdentifierInput input) {
// call API X...
return customer;
} For the Reactor context, the GraphQL Java engine which invokes each |
I have updated my sample repo with more concrete examples on new Using the above updated sample:
Hopefully I have illustrated the problem more clear. |
I was able to achieve what I'm after, albeit rather ugly IMO. Using @QueryMapping
public Mono<IdealCustomer> idealCustomer(@Argument CustomerIdentifierInput input, DataFetchingFieldSelectionSet selectionSet, GraphQLContext graphQLContext) {
var customer = new IdealCustomer();
customer.setId(input.getId());
var mono = Mono.just(customer);
if (selectionSet.contains("firstName")) {
Mono<IdealCustomer> finalMono = mono;
mono = this.customerInfoService.getCustomerInfo(input.getId()).flatMap(info -> {
graphQLContext.put("customerInfo", info);
return finalMono;
});
}
return mono;
} then in other data fetchers: @SchemaMapping
public Mono<String> firstName(IdealCustomer customer, GraphQLContext graphQLContext) {
return getFromContextOrGet(customer, graphQLContext).map(CustomerInfo::getFirstName);
}
@SchemaMapping
public Mono<String> lastName(IdealCustomer customer, GraphQLContext graphQLContext) {
return getFromContextOrGet(customer, graphQLContext).map(CustomerInfo::getLastName);
}
private Mono<CustomerInfo> getFromContextOrGet(IdealCustomer customer, GraphQLContext context) {
Object cached = context.get("customerInfo");
if (cached != null) {
return Mono.just((CustomerInfo) cached);
}
return this.customerInfoService.getCustomerInfo(customer.getId());
} This allows me to use the second schema I posted above. However, some of the downstream responses are huge (100+ fields), so I think the first schema would be a better approach in the end. |
Thanks for the sample @ciscoo. Here is what I made of it. Keep the separate public class IdealCustomer {
private int id;
private CustomerInfo info;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFirstName() {
return info.getFirstName();
}
public String getLastName() {
return info.getLastName();
}
public IdealCustomer initCustomerInfo(CustomerInfo info) {
this.info = info;
return this;
}
public boolean hasCustomerInfo() {
return this.info != null;
}
} Then the @QueryMapping
public Mono<IdealCustomer> idealCustomer(@Argument CustomerIdentifierInput input) {
var customer = new IdealCustomer();
customer.setId(input.getId());
return Mono.just(customer);
} and @Controller
public class IdealCustomerInfoController {
private final CustomerInfoService customerInfoService;
public IdealCustomerInfoController(CustomerInfoService customerInfoService) {
this.customerInfoService = customerInfoService;
}
@SchemaMapping
public Mono<String> firstName(IdealCustomer customer) {
return initCustomerInfo(customer).map(IdealCustomer::getFirstName);
}
@SchemaMapping
public Mono<String> lastName(IdealCustomer customer) {
return initCustomerInfo(customer).map(IdealCustomer::getLastName);
}
private Mono<IdealCustomer> initCustomerInfo(IdealCustomer customer) {
return (customer.hasCustomerInfo() ? Mono.just(customer) :
this.customerInfoService.getCustomerInfo(customer.getId()).map(customer::initCustomerInfo));
}
} As an alternative, to improve on that, is to peek in the selection set, which means you don't need a separate @QueryMapping
public Mono<IdealCustomer> idealCustomer(@Argument CustomerIdentifierInput input, DataFetchingFieldSelectionSet selectionSet) {
var customer = new IdealCustomer();
customer.setId(input.getId());
return selectionSet.containsAnyOf("firstName", "lastName") ?
this.customerInfoService.getCustomerInfo(customer.getId()).map(customer::initCustomerInfo) :
Mono.just(customer);
} |
Yes, your last sample peeking in the selection set is what I my comment was. 😄 Seems I accidentally reopened this issue after closing which I did not notice I did. So, closing again since a solution was found. |
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
andcompleted
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.However, seems this approach does not work since the logs show (2) requests being sent:
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.to
The text was updated successfully, but these errors were encountered: