I’m using WebFlux and WebClient and I need to consume two APIs and merge its responses.
The first API receive type and document number and returns a list with one element that contains customer data (that’s how it’s defined).
The second API receive a client id and returns a customer payments list.
I need to consume these two APIs and return an entity with the customer data and their payments.
API Customer response
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 Payment Response
public class PaymentResponseApi { private int customerId; private LocalDate paymentDate; private float amount; private String paymentType; }
finally I should have this
CustomerResponse.java
public class CustomerResponse { private int customerId; private String documentNumber; private String documentType; private String firstName; private String lastName; private List<PaymentResponseApi> payments; }
I have a proxy class that is responsible for making the API call
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); } }
And a service that is responsible for merge responses 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()); }); } }
Advertisement
Answer
Two ways to resolve this:
- Using nested maps:
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(); }); }); }
- Using zip: Since you need info from the response of first API in your second API, you need to chain these two together. Now since they are async calls, you need a flatMap or a variant of flatMap called flatMapMany which emits more than one elements (which exactly what your 2nd API is doing). Next, you need both the responses to build your CustomerResponse i.e you need to zip the two reactive stream responses from the two APIs.
Thus basically using Method2 you need this:
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(); }); }
Demerit of Method2: Api1 i.e customer API will be be subscribed twice.