用Vertx实现客户端分页API调用的最佳方法是什么?

时间:2020-01-29 22:29:23

标签: rest pagination vert.x

我有一个Vert.x Web服务,该服务需要对外部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来表示单个调用的结果,然后将它们与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 个答案:

答案 0 :(得分:1)

您正在尝试对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());
       }
  });

请注意,我实际上并未编译您的代码,因此可能需要进行更多更改才能使其真正起作用。

答案 1 :(得分:0)

在这个问题中,我误解了我要连接的API,并且“下一个”字段在两次调用之间不会更改。因此,这个问题简化为“如何在Vertx中实现异步客户端分页,在每个分页调用之前我 都知道URL?”。我接受Alexey的回答是因为它回答了原始问题,并张贴了我在下面使用的粗略代码,以防万一使用相同用例的人:

// 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();
}