У меня есть библиотека, которая используется клиентом, и они передают объект 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выглядит странным? - Нужно ли иметь двух исполнителей, подобных мне в моем коде? Есть ли лучший способ решить эту проблему или любое изменение упрощения/дизайна, которое мы можем здесь сделать?
Я упростил код, чтобы идея поняла, что я пытаюсь сделать.