I have a service which sends a set of queries to the server in a single call and acts on the decisions from the server. I will first introduce what I already have and the questions will follow.
public class Item {
int id;
int version;
String description;
}
public class RequestObject {
Item key;
List<Item> candidates;
}
public class ResultObject {
Item key,
Item candidate,
Boolean isActionable;
}
public class Client {
List<ResultObject> post(RequestObject request) {
// Makes a rest call to the server and returns the results
}
}
/** Server side **/
public class ServiceServlet {
private ActionDecider actionDecider;
List<ResultObject> processRequest(RequestObject request) {
List<ResultObject> results = new ArrayList<>();
for(Item c : request.candidates) {
// decider reads values of key and c from database, returns the results
results.add(actionDecider.decide(request.key, c));
}
return results;
}
}
/** Client side **/
public class ClientService {
private ResultUpdater updater;
public void processMessage(Item key, List<Item> candidates) {
RequestObject request = new RequestObject(key, candidates);
for(ResultObject result : client.post(request)) {
if(result.isActionable) {
// update values of key and c in database, if key provided has a different version that in the database, throws exception
updater.update(result.key, result.candidate);
}
}
}
}
Note:
- The number of candidates in a request is around 100. And on average we get around 5 actionable pairs back.
actionDecider.decide
is an expensive operation so we want to avoid running it unnecessarily more than once for the same pair of items.
Problem: When there are multiple actionable results and updater updates the key the first time, the value in the database changes and hence the rest of the results are stale. The rest of the messages have to be re-evaluated by calling the server side service before they can be updated.
One simple solution would be to send each one candidate at the time as a request to the server, update the result if actionable and continue with the next candidate. However, this makes K number of requests instead of making a single request. This means we will be incurring almost K times more the network latency.
public class ClientService {
private ResultUpdater updater;
public void processMessage(Item key, List<Item> candidates) {
for(Item candidate : candidates) {
RequestObject request = new RequestObject(key, candidate);
ResultObject result = client.post(request).get(0);
if(result.isActionable) {
// update values of key and c in database, if key provided has a different version that in the database, throws exception
updater.update(result.key, result.candidate);
}
}
}
}
Alternative solution I am thinking is the following. Since we own the server service as well, we can modify the request object to contain a flag asking the server to return the first actionable result skipping the remaining candidates in the request. Similarly, we can come up with a different client which keeps track of the candidates that it has already sent to the server for comparison and what are the remaining ones.
public class OptimizedClient {
public final RequestObject request;
private List<Item> remainingCandidates;
public OptimizedClient(RequestObject request) {
this.request = request;
this.remainingCandidates = request.candidates;
}
public ResultObject getNextActionable() {
// makes call to the server with the remaining candidates and check if it got a first set of matched results
RequestObject remainingRequest = new RequestObject(request.key, request.remaningCandidates);
// make rest call
List<ResultObject> results = restClient.post(remainingRequest);
if(results != null) {
int from = remainingCandidates.indexOf(results.get(0)) + 1;
int to = remainingCandidates.size();
remainingRequest = remainingCandidates.subList(from, to);
return results.get(0);
}
return null;
}
}
Is there a standard pattern for this kind of problem? If not, can I make improvements to my proposed optimized solution?