使用 WebClient/Spring Boot 2 将 REST 响应映射到 Mono<SomeClass> 的正确方法

The correct way to map a REST response to a Mono<SomeClass> using WebClient/ Spring Boot 2

我有一个控制器,它要求服务到达服务端点以获取帐号列表,然后为每个帐号联系另一个服务并检索有关该帐户的其他数据。 'happy path' 本质上是这样的:

控制器

@ApiOperation(value = "getRelatedAccounts")
@GetMapping(value = "/relatedaccounts")
public ResponseEntity<RelatedAccounts> getRelatedAccounts(@RequestParam String accountNumber) {
    return new ResponseEntity<>(myService.getRelatedAccounts(accountNumber), HttpStatus.OK);
}

服务

public RelatedAccounts getRelatedAccounts(String accountNumber) {
    // blah blah, hit the endpoint and get my response of a list of account numbers in relatedAccountsList
    Flux<AccountInformation> accountInfoFlux = Flux.fromIterable(relatedAccountsList)
        .parallel()
        .runOn(Schedulers.elastic())
        .flatMap(this::getAccountInformation)
        .ordered(Comparator.comparing(RelatedAccounts::getCorrectOrder)) // blah blah, convert from ParallelFlux to Flux
}

其他服务

public Mono<AccountInformation> getAccountInformation(String accountNumber) {
    WebClient webClient = WebClient.builder()
            .baseUrl("http://myurl.com")
            .build();

    return webClient
            .get()
            .uri(uriBuilder -> uriBuilder
                    .queryParam("accountNumber", accountNumber)
                    .build()
            ).retrieve()
            .bodyToMono(AccountInformation.class) // This doesn't work for us, we get a very complex object as a response and need to parse a few fields.
}

我在 Google 上花了一整天,但我没有看到其他人解析他们返回的响应,他们只是神奇地将其直接映射到完美创建的 class 上。我们没有那个选项,我们需要从响应主体中提取 accountName 并将其放入 AccountInformation 对象中。有人知道怎么做吗?

我的建议是继续使用 bodyToMono(AccountInformation.class),然后使用 Monos map and zip 映射到您的简单对象。

  1. 创建一个 class 来表示复杂的 AccountInformation,但只包含您需要的信息(不要包含您不需要的对象字段)。
  2. 使用您的其他服务(returns Mono)的确切代码
  3. 您可以使用 .zip 将您的简单对象与 AccountInformation 对象结合起来。
  4. 因此,您的简单对象只包含复杂对象所需的数据

例如

其他服务保持不变:

public Mono<AccountInformation> getAccountInformation(String accountNumber) {
    WebClient webClient = WebClient.builder()
            .baseUrl("http://myurl.com")
            .build();

    return webClient
            .get()
            .uri(uriBuilder -> uriBuilder
                    .queryParam("accountNumber", accountNumber)
                    .build()
            ).retrieve()
            .bodyToMono(AccountInformation.class)
}

创建缺失的class(表示其他服务的结果),跳过所有您不需要的信息字段

import java.util.List;

public class AccountInformation {
    private String infoYouWant;
    private List<AccountDetails> otherNestedInfoYouWant;
// getters/setters skipped for easier read
}

将您的简单对象与来自其他服务的对象合并:

        Mono<AccountInformation> accountInformation = someService.getAccountInformation("fillMeWithAccountNumber");
        Mono<SimpleObject> simpleObjectWithAccountInformation = Mono.zip(simpleObject, accountInformation).map(objects -> {
            SimpleObject simpleObject = objects.getT1();
            simpleObject.setAccountNumber(objects.getT2().getInfoYouWant());
            return simpleObject;
        });

我们最终采用了一种非常丑陋但可扩展且有效的方法。在我们的其他服务中:

public Mono<AccountInformation> getAccountInformation(String accountNumber) {
    WebClient webClient = WebClient.builder()
            .baseUrl("http://myurl.com")
            .build();

    return webClient
            .get()
            .uri(uriBuilder -> uriBuilder
                    .queryParam("accountNumber", accountNumber)
                    .build()
            ).retrieve()
            .bodyToMono(String.class) // just get the response as a JSON string
            .map(jsonString -> {
                ObjectMapper mapper = new ObjectMapper();
                JsonNode root = mapper.readTree(jsonString);
                // parse/ map out all your information here
                return myCustomMappedObject;
            })
}