У меня есть библиотека, которая используется клиентом, и они передают объект DataRequest
, который имеет userid
, timeout
и некоторые другие поля в нем. Теперь я использую этот объект DataRequest
для создания URL-адреса, а затем я делаю HTTP-вызов с помощью RestTemplate
, и моя служба возвращает ответ JSON, который я использую для создания объекта DataResponse
и возвращает этот объект DataResponse
назад к ним.
Ниже мой класс DataClient
, используемый клиентом, передавая ему объект DataRequest
. Я использую значение тайм-аута, переданное клиентом в DataRequest
, чтобы пропустить запрос, если он занимает слишком много времени в методе getSyncData
.
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
класс:
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
}
}
В настоящее время мой класс DataFetcherTask
отвечает за один ключ DataRequest
, как показано выше.
Проблема: -
Теперь у меня небольшое изменение дизайна. Клиент передаст объект DataRequest
(например, keyA) в мою библиотеку, а затем я сделаю новый HTTP-вызов другой службе (которую я не делаю в своем текущем проекте), используя идентификатор пользователя, присутствующий в DataRequest
(keyA) объект, который вернет мне список идентификаторов пользователя, поэтому я буду использовать этот идентификатор пользователя и сделать несколько других DataRequest
(keyB, keyC, keyD) объектов по одному для каждого идентификатора пользователя, возвращаемого в ответ. И тогда у меня будет объект List<DataRequest>
, который будет иметь объекты keyB, keyC и keyD DataRequest
. Максимальным элементом в List<DataRequest>
будет три, все.
Теперь для каждого из объектов DataRequest
в List<DataRequest>
я хочу выполнить выше DataFetcherTask.call
метод параллельно, а затем сделать List<DataResponse>
, добавив каждый DataResponse
для каждого ключа. Поэтому у меня будет три параллельных вызова DataFetcherTask.call
. Идея этого параллельного вызова состоит в том, чтобы получить данные для всех этих максимальных трех ключей в одном глобальном значении таймаута.
Итак, мое предложение - DataFetcherTask
класс вернет обратно List<DataResponse>
объект вместо DataResponse
, а затем изменит подпись метода getSyncData
и getAsyncData
. Итак, вот алгоритм:
- Использовать объект DataRequest, переданный клиентом, сделать
List<DataRequest>
, вызвав другую службу HTTP. - Сделайте параллельный вызов для каждого метода
DataRequest
вList<DataRequest>
доDataFetcherTask.call
и верните объектList<DataResponse>
клиенту вместоDataResponse
.
Таким образом, я могу применить один и тот же глобальный тайм-аут на шаге 1 вместе с шагом 2. Если какой-либо из вышеперечисленных шагов занимает время, мы просто перейдем к методу getSyncData
.
DataFetcherTask
после изменения дизайна:
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..
}
}
Теперь мой вопрос -
- Должно ли это быть таким? Каков правильный дизайн для решения этой проблемы? Я имею в виду, что метод
call
в другом методеcall
выглядит странным? - Нужно ли иметь двух исполнителей, подобных мне в моем коде? Есть ли лучший способ решить эту проблему или любое изменение упрощения/дизайна, которое мы можем здесь сделать?
Я упростил код, чтобы идея поняла, что я пытаюсь сделать.