Объедините два ответа api в один с помощью Webclient - Webflux

Я использую WebFlux и WebClient, и мне нужно использовать два API и объединить их ответы.

Первый API получает тип и номер документа и возвращает список с одним элементом, который содержит данные о клиенте (как это определено).

Второй API получает идентификатор клиента и возвращает список платежей клиентов.

Мне нужно использовать эти два API и вернуть объект с данными клиентов и их платежами.

Ответ клиента API

public class CustomerResponseApi {
    private List<CustomerApi> clientList;
}
public class CustomerApi {
    private int customerId;
    private String documentNumber;
    private String documentType;
    private String firstName;
    private String lastName;
}

Ответ об оплате через API

public class PaymentResponseApi {
    private int customerId;
    private LocalDate paymentDate;
    private float amount;
    private String paymentType;
}

наконец-то у меня должно быть это

CustomerResponse.java

public class CustomerResponse {
    private int customerId;
    private String documentNumber;
    private String documentType;
    private String firstName;
    private String lastName;
    
    private List<PaymentResponseApi> payments;
}

У меня есть прокси-класс, который отвечает за вызов API

CustomerProxy.java

public class CustomerProxy {

    @Value("${api.base-url}")
    private String baseUrl;

    public Mono<CustomerResponseApi> getCustomer(String documentType, String documentNumber) {
        log.info("baseUrl: {}", baseUrl);
        WebClient webClient = WebClient.create(baseUrl);

        return webClient.get()
                .uri(uri -> uri
                        .path("/customers")
                        .queryParam("documentNumber", documentNumber)
                        .queryParam("documentType", documentType)
                        .build()
                )
                .retrieve()
                .bodyToMono(CustomerResponseApi.class);
    }
}

PaymentProxy.java

public class PaymentProxy {

    @Value("${api.base-url}")
    private String baseUrl;

    public Flux<PaymentResponseApi> getCustomerPayment(int customerId) {
        log.info("baseUrl: {}", baseUrl);
        WebClient webClient = WebClient.create(baseUrl);

        return webClient.get()
                .uri(uri -> uri
                        .path("/payments")
                        .queryParam("customerId", customerId)
                        .build()
                )
                .retrieve()
                .bodyToFlux(PaymentResponseApi.class);
    }
}

И сервис, отвечающий за объединение ответов CustomerServiceImpl.java

public class CustomerServiceImpl implements CustomerService {
    
    @Autowired
    private CustomerProxy customerProxy;
    
    @Autowired
    private PaymentProxy paymentProxy;

    @Override
    public Mono<CustomerResponse> getCustomerAndPayments(String documentType, String documentNumber) {
        return customerProxy.getCustomer(documentType, documentNumber).flatMap(resp -> {
            CustomerApi customerApi = resp.getClientList().get(0); //always returns one customer
            
            // Here is my problem, because getCustomerPayment method returns a Flux
            List<PaymentResponseApi> payments = paymentProxy.getCustomerPayment(customerApi.getCustomerId());
            
            CustomerResponseBuilder customerBuilder = CustomerResponse.builder()
                    .customerId(customerApi.getCustomerId())
                    .documentNumber(customerApi.getDocumentNumber())
                    .documentType(customerApi.getDocumentType())
                    .firstName(customerApi.getFirstName())
                    .lastName(customerApi.getLastName())
                    .payments(payments);
            
            return Mono.just(customerBuilder.build());
        });
    }
}

Что я должен делать? введите описание изображения здесь


person Jhon Anaya Valiente    schedule 14.07.2020    source источник


Ответы (2)


Два способа решить эту проблему:

  1. Использование вложенных карт:
public Mono<CustomerResponse> getCustomerAndPayments(String documentType, String documentNumber) {

    return customerProxy.getCustomer(documentType, documentNumber)
        .map(resp -> resp.getClientList().get(0))
        .flatMap(customerApi -> {
          Flux<PaymentResponseApi> paymentProxyFlux = paymentProxy.getCustomerPayment(customerApi.getCustomerId());
          return paymentProxyFlux.collectList()
              .map(payments -> {
                CustomerResponseBuilder customerBuilder = CustomerResponse.builder()
                    .customerId(customerApi.getCustomerId())
                    .documentNumber(customerApi.getDocumentNumber())
                    .documentType(customerApi.getDocumentType())
                    .firstName(customerApi.getFirstName())
                    .lastName(customerApi.getLastName())
                    .payments(payments);
                return customerBuilder.build();
              });
        });


  }
  1. Использование zip: Поскольку вам нужна информация из ответа первого API во втором API, вам необходимо связать эти два вместе. Теперь, поскольку они являются асинхронными вызовами, вам понадобится flatMap или вариант flatMap, называемый flatMapMany, который генерирует более одного элемента (что и делает ваш второй API). Затем вам понадобятся оба ответа для создания вашего CustomerResponse, то есть вам нужно заархивировать два реактивных потоковых ответа от двух API.

Таким образом, в основном, используя Method2, вам нужно это:

  public Mono<CustomerResponse> getCustomerAndPayments(String documentType, String documentNumber) {


    Mono<CustomerApi> customerApiMono =  customerProxy.getCustomer(documentType, documentNumber)
        .map(resp -> resp.getClientList().get(0));
    Mono<List<PaymentResponseApi>> paymentResponseApiListMono = customerApiMono
        .flatMapMany(customerApi -> paymentProxy.getCustomerPayment(customerApi.getCustomerId()))
        .collectList();

    return customerApiMono.zipWith(paymentResponseApiListMono)
        .map(tuple -> {
          CustomerApi customerApi = tuple.getT1();
          List<PaymentResponseApi> payments = tuple.getT2();
          CustomerResponseBuilder customerBuilder = CustomerResponse.builder()
              .customerId(customerApi.getCustomerId())
              .documentNumber(customerApi.getDocumentNumber())
              .documentType(customerApi.getDocumentType())
              .firstName(customerApi.getFirstName())
              .lastName(customerApi.getLastName())
              .payments(payments);
          return customerBuilder.build();
        });

  }

Недостаток метода 2: Api1, то есть клиентский API, будет подписан дважды.

person Abhinaba Chakraborty    schedule 15.07.2020
comment
@AbhinabaChakraborty, вы можете использовать Mono.zip (..) и передать два кортежа. - person global_warming; 16.07.2020
comment
@global_warming как это имеет значение? - person Abhinaba Chakraborty; 16.07.2020
comment
@AbhinabaChakraborty customerApi не будет подписан дважды в этом случае, я думаю, что использование Mono.zip с комбинатором можно использовать для возврата Mono ‹CustomerResponse›, как того хочет OP. - person global_warming; 16.07.2020

В этой ситуации вы можете кэшировать результат первого вызова, чтобы предотвратить вызов API дважды.

Иногда также проще работать с оператором zip, создав класс-оболочку, который не работает с кортежем. В этом случае CustomerWithPayments :

public class CustomerWithPayments {
    private final CustomerApi customerApi;
    private final List<PaymentResponseApi> paymentResponseApis;
}

Решение с кешированием результата API:

public Mono<CustomerResponse> getCustomerAndPayments(String documentType, String documentNumber) {
    Mono<CustomerApi> customerMono = customerProxy.getCustomer(documentType, documentNumber)
            .map(resp -> resp.getClientList().get(0))
            .cache();
    Mono<List<PaymentResponseApi>> paymentResponseMono = customerMono.map(CustomerApi::getCustomerId)
            .flatMapMany(paymentProxy::getCustomerPayment)
            .collectList();

    return customerMono.zipWith(paymentResponseMono, CustomerWithPayments::new)
            .map(customerWithPayments -> {
                CustomerApi customer = customerWithPayments.getCustomerApi();
                List<PaymentResponseApi> payments = customerWithPayments.getPaymentResponseApis();
                return CustomerResponse.builder()
                        .customerId(customer.getCustomerId())
                        .documentNumber(customer.getDocumentNumber())
                        .documentType(customer.getDocumentType())
                        .firstName(customer.getFirstName())
                        .lastName(customer.getLastName())
                        .payments(payments)
                        .build();
            });
}
person mslowiak    schedule 15.07.2020
comment
Вы уверены, что это предотвращает двойной вызов CustomerApi? Вы проверили ввод журнала? - person Abhinaba Chakraborty; 15.07.2020
comment
@AbhinabaChakraborty да, я уверен - person mslowiak; 15.07.2020