Каков наилучший способ реализации вызовов API на стороне клиента с помощью Vertx? - PullRequest
0 голосов
/ 30 января 2020

У меня есть веб-сервис Vert.x, который должен выполнить серию постраничных вызовов внешнего API. Внешний сервис реализует разбиение на страницы путем включения в каждый ответ поля «следующее», которое представляет собой прямую ссылку на следующую страницу данных, а также подсчета общего количества страниц, необходимых для извлечения всех данных. Вот пример ответа:

"pagination": {
  "count": 1000,
  "totalPages": 112,
  "next": "https://some-host.com?next=some-long-alphanumeric-hash"
},
"data": [ ... ]

После выполнения первого вызова API я знаю общее количество последующих вызовов (в данном примере 111), а также URL-адрес для получения следующей страницы данных. , В синхронной среде я мог бы просто сделать что-то вроде этого:

Collection aggregatedResults;
int count = 0;
String nextUrl = "";
while (count <= total pages) {
   make next request
   add the chunk of data from this response to the collection
   store the next URL in local variable
   increment count
}

Моя стратегия с Vertx состоит в том, чтобы использовать Future s для представления результата отдельных вызовов, а затем объединить их в цепочку вместе с CompositeFuture.all() , Это примерно то, что у меня есть (некоторый код опущен для экономии места):

private String nextUrl; // global String

doFirstCall(client).setHandler(async -> {
    if (async.failed()) {
      // blah
    } else {
        Response response = async.result();
        int totalPages = response.getTotalPages();
        next = response.getNext();

        List<Future> paginatedFutures = IntStream
                .range(0, totalPages - 1)
                .mapToObj(i -> {
                    Promise<Response> promise = Promise.promise();
                    doIndividualPaginatedCall(client, next)
                            .setHandler(call -> {
                                if (call.succeeded()) {
                                    Response chunk = call.result();
                                    next = chunk.getNext(); // store the next URL in global string so it can be accessed within the loop
                                    promise.complete(chunk);
                                } else {
                                    promise.fail(call.cause());
                                }
                            });
                    return promise.future();
                })
                .collect(Collectors.toList());

        CompositeFuture.all(paginatedFutures).setHandler(all -> {
            if (all.succeeded()) {
                // Do something with the aggregated responses
            }
        });
    }
});

Когда я запускаю этот код, первый вызов всегда выполняется успешно, и я успешно сохраняю следующий URL. Затем каждый последующий вызов нумерации страниц разбивается на тот же URL-адрес, который был получен при первом вызове, и я вижу журналы, подобные этому:

Call succeeded. i: 16, next: https://blah.com/blah?filter=next(DnF1ZXJ5VGhlbkZldGNoBQAAAAAAlMYVFjdaM2ducHBaVGJHeWV5ZjRzNGRQMXcAAAAAAJTGNhYzcWlRTDEyeVJZS05PeV84QkJlLTVnAAAAAACUxjYWa3UzUkx1MXZURG1Pc2E5WGt5RG9pdwAAAAAAlMY2FnY4TVhXajlqUmMtWEQwWU1naGZFN3cAAAAAAJTGVxZCWWFUV19XR1RXQ05DRkI0NGw4M0xB)
Call succeeded. i: 17, next: https://blah.com/blah?filter=next(DnF1ZXJ5VGhlbkZldGNoBQAAAAAAlMYVFjdaM2ducHBaVGJHeWV5ZjRzNGRQMXcAAAAAAJTGNhYzcWlRTDEyeVJZS05PeV84QkJlLTVnAAAAAACUxjYWa3UzUkx1MXZURG1Pc2E5WGt5RG9pdwAAAAAAlMY2FnY4TVhXajlqUmMtWEQwWU1naGZFN3cAAAAAAJTGVxZCWWFUV19XR1RXQ05DRkI0NGw4M0xB)
Call succeeded. i: 18, next: https://blah.com/blah?filter=next(DnF1ZXJ5VGhlbkZldGNoBQAAAAAAlMYVFjdaM2ducHBaVGJHeWV5ZjRzNGRQMXcAAAAAAJTGNhYzcWlRTDEyeVJZS05PeV84QkJlLTVnAAAAAACUxjYWa3UzUkx1MXZURG1Pc2E5WGt5RG9pdwAAAAAAlMY2FnY4TVhXajlqUmMtWEQwWU1naGZFN3cAAAAAAJTGVxZCWWFUV19XR1RXQ05DRkI0NGw4M0xB)

TLDR: как выполнить серию вызовов API с разбиением на страницы, где URL изменяется между каждым вызовом и не известен, пока не завершится предыдущий вызов? Я пытался использовать CompositeFuture.join, но тот же эффект. Я знаю, что для последовательной компоновки вы должны использовать compose(), но как мне составить неизвестное количество вызовов функций?

Ответы [ 2 ]

1 голос
/ 30 января 2020

Вы пытаетесь изменить next

if (call.succeeded()) {
    Response chunk = call.result();
    next = chunk.getNext(); // store the next URL in global string so it can be accessed within the loop
    promise.complete(chunk);
}

Но вы на самом деле повторно используете то же значение, которое получили в первый раз:

next = response.getNext();

Это потому, что все ваши звонки вызываются задолго до того, как хотя бы один из них вернется.

Поскольку вы не можете узнать значение next до возврата предыдущего вызова, вам придется реализовать его рекурсивным способом и отбросить map :

doIndividualPaginatedCall(client, next)
   .setHandler(call -> {
        if (call.succeeded()) {
            Response chunk = call.result();
            next = chunk.getNext(); // store the next URL in global string so it can be accessed within the loop
            promise.complete(chunk);
            doIndividualPaginatedCall(client, next);
       } else {
           promise.fail(call.cause());
       }
  });

Обратите внимание, что я на самом деле не скомпилировал ваш код, поэтому может потребоваться больше изменений, чтобы он действительно работал.

0 голосов
/ 10 февраля 2020

Оказывается, я неправильно понял API, к которому я подключаюсь, в этом вопросе, и поле "next" не меняется между вызовами. Следовательно, этот вопрос сводится к «Как реализовать асинхронную нумерацию страниц на стороне клиента в Vertx, где я делаю знаю URL-адрес перед каждым вызовом с нумерацией страниц?». Я принимаю ответ Алексея, потому что он ответил на исходный вопрос, и публикую примерный код, который я использовал ниже, на случай, если это поможет кому-либо с таким же вариантом использования:

// start()
doFirstCall(client).setHandler(async -> {
  if (async.succeeded()) {
    Response response = async.result();
    final int totalPages = response.totalPages();
    final String next = response.next();

    // Fire off 'totalPages' async calls and wait for them to come back
    List<Future> paginatedFutures = IntStream
      .range(0, totalPages)
      .mapToObj(i -> {
         Promise<Response> promise = Promise.promise();
         doPaginatedCall(client).setHandler(call -> {
            if (call.succeeded()) {
              promise.complete(call.result());
            }
         });
         return promise.future();
      }).collect(Collectors.toList());

   // Wait for all HTTP calls to come back before continuing
   CompositeFuture.join(paginatedFutures).setHandler(all -> {
      if (all.succeeded()) {
         // Do something with all of the aggregated calls
      }
  });
  }
});

private Future<Response> doFirstCall(WebClient client) {
   Promise<Response> promise = Promise.promise();

   // If call succeeded, promise.complete(response), otherwise fail

   return promise.future();
}

private Future<Response> doPaginatedCall(WebClient client, String nextUrl) {
   Promise<Response> promise = Promise.promise();

   // If call succeeded, promise.complete(response), otherwise fail

   return promise.future();
}

...