I have a library which is being used by customer and they are passing DataRequest
object which has userid
, timeout
and some other fields in it. Now I use this DataRequest
object to make a URL and then I make an HTTP call using RestTemplate
and my service returns back a JSON response which I use it to make a DataResponse
object and return this DataResponse
object back to them.
Below is my DataClient
class used by customer by passing DataRequest
object to it. I am using timeout value passed by customer in DataRequest
to timeout the request if it is taking too much time in getSyncData
method.
public class DataClient implements Client {
private RestTemplate restTemplate = new RestTemplate();
// first executor
private ExecutorService service = Executors.newFixedThreadPool(15);
@Override
public DataResponse getSyncData(DataRequest key) {
DataResponse response = null;
Future<DataResponse> responseFuture = null;
try {
responseFuture = getAsyncData(key);
response = responseFuture.get(key.getTimeout(), key.getTimeoutUnit());
} catch (TimeoutException ex) {
response = new DataResponse(DataErrorEnum.CLIENT_TIMEOUT, DataStatusEnum.ERROR);
responseFuture.cancel(true);
// logging exception here
}
return response;
}
@Override
public Future<DataResponse> getAsyncData(DataRequest key) {
DataFetcherTask task = new DataFetcherTask(key, restTemplate);
Future<DataResponse> future = service.submit(task);
return future;
}
}
DataFetcherTask
class:
public class DataFetcherTask implements Callable<DataResponse> {
private DataRequest key;
private RestTemplate restTemplate;
public DataFetcherTask(DataRequest key, RestTemplate restTemplate) {
this.key = key;
this.restTemplate = restTemplate;
}
@Override
public DataResponse call() throws Exception {
// In a nutshell below is what I am doing here.
// 1. Make an url using DataRequest key.
// 2. And then execute the url RestTemplate.
// 3. Make a DataResponse object and return it.
// I am calling this whole logic in call method as LogicA
}
}
As of now my DataFetcherTask
class is responsible for one DataRequest
key as shown above..
Problem Statement:-
Now I have a small design change. Customer will pass DataRequest
(for example keyA) object to my library and then I will make a new http call to another service (which I am not doing in my current design) by using user id present in DataRequest
(keyA) object which will give me back list of user id's so I will use those user id's and make few other DataRequest
(keyB, keyC, keyD) objects one for each user id returned in the response. And then I will have List<DataRequest>
object which will have keyB, keyC and keyD DataRequest
object. Max element in the List<DataRequest>
will be three, that's all.
Now for each of those DataRequest
object in List<DataRequest>
I want to execute above DataFetcherTask.call
method in parallel and then make List<DataResponse>
by adding each DataResponse
for each key. So I will have three parallel calls to DataFetcherTask.call
. Idea behind this parallel call is to get the data for all those max three keys in the same global timeout value.
So my proposal is - DataFetcherTask
class will return back List<DataResponse>
object instead of DataResponse
and then signature of getSyncData
and getAsyncData
method will change as well. So here is the algorithm:
- Use DataRequest object passed by customer to make
List<DataRequest>
by calling another HTTP service. - Make a parallel call for each
DataRequest
inList<DataRequest>
toDataFetcherTask.call
method and returnList<DataResponse>
object to customer instead ofDataResponse
.
With this way, I can apply same global timeout on step 1 along with step 2 as well. If either of above step is taking time, we will just timeout in getSyncData
method.
DataFetcherTask
class after design change:
public class DataFetcherTask implements Callable<List<DataResponse>> {
private DataRequest key;
private RestTemplate restTemplate;
// second executor here
private ExecutorService executorService = Executors.newFixedThreadPool(10);
public DataFetcherTask(DataRequest key, RestTemplate restTemplate) {
this.key = key;
this.restTemplate = restTemplate;
}
@Override
public List<DataResponse> call() throws Exception {
List<DataRequest> keys = generateKeys();
CompletionService<DataResponse> comp = new ExecutorCompletionService<>(executorService);
int count = 0;
for (final DataRequest key : keys) {
comp.submit(new Callable<DataResponse>() {
@Override
public DataResponse call() throws Exception {
return performDataRequest(key);
}
});
}
List<DataResponse> responseList = new ArrayList<DataResponse>();
while (count-- > 0) {
Future<DataResponse> future = comp.take();
responseList.add(future.get());
}
return responseList;
}
// In this method I am making a HTTP call to another service
// and then I will make List<DataRequest> accordingly.
private List<DataRequest> generateKeys() {
List<DataRequest> keys = new ArrayList<>();
// use key object which is passed in contructor to make HTTP call to another service
// and then make List of DataRequest object and return keys.
return keys;
}
private DataResponse performDataRequest(DataRequest key) {
// This will have all LogicA code here which is shown in my original design.
// everything as it is same..
}
}
Now my question is -
- Does it have to be like this? What is the right design to solve this problem? I mean having
call
method in anothercall
method looks weird? - Do we need to have two executors like I have in my code? Is there any better way to solve this problem or any kind of simplification/design change we can do here?
I have simplified the code so that idea gets clear what I am trying to do..
In my previous comment I talked about ability of returning partial result even after global timeout. – dezhik Dec 25 '15 at 17:38