I'm writing a service that calls 20 external vendor APIs, aggregates that data and writes it to a blob storage. This is how I am calling each api, after I am using Mono.zip() and writing that result into a blob storage. However I feel that the way I am writing the code is really redundant, specifically the error handling using .onErrorResume() and .doOnSuccess() Is there a way I can maek this code cleaner by using generics or utilizing inheritance some way? I just dont want hundreds of lines of code that are basically doing the same thing...
Mono<MailboxProvidersDTO> mailboxProvidersDTOMono = partnerAsyncService.asyncCallPartnerApi(getMailboxProvidersUrl, MailboxProvidersDTO.class)
.retryWhen(getRetrySpec())
//need to have error handling for all 20 api calls
.doOnSuccess(res -> {
log.info(Logger.EVENT_SUCCESS, "Mailbox Providers report successfully retrieved.");
res.setStatus("Success");
})
.onErrorResume(BusinessException.class, ex -> {
log.error(Logger.EVENT_FAILURE, ex.getMessage());
MailboxProvidersDTO audienceExplorerDTO = new MailboxProvidersDTO();
audienceExplorerDTO.setStatus("Failed");
return Mono.just(audienceExplorerDTO);
});
Mono<TimeZonesDTO> timeZonesDTOMono = partnerAsyncService.asyncCallPartnerApi(getTimeZonesUrl, TimeZonesDTO.class);
Mono<RegionsDTO> regionsDTOMono = partnerAsyncService.asyncCallPartnerApi(getRegionsUrl, RegionsDTO.class);
Mono<AudienceExplorerDataSourcesDTO> audienceExplorerDataSourcesDTOMono = partnerAsyncService.asyncCallPartnerApi(getAudienceExplorerDataSourcesUrl, AudienceExplorerDataSourcesDTO.class);
...
CodePudding user response:
You can effectively use generics to refactor your code. You can couple functional interfaces and Generics to create what you need:
- On your example, you need both to "setStatus" and create new instances of different classes. You could then create a utility function to add onSuccess/onFailure behaviours over your initial data fetching Mono:
public <T> Mono<T> withRecovery(Mono<T> fetchData, BiConsumer<T, String> setStatus, Supplier<T> createFallbackDto) {
return fetchData
.doOnSuccess(result -> {
log.info...
setStatus.accept(result, "Success");
})
.doOnError(BusinessException.class, err -> {
log.error...
T fallback = createFallbackDto.get();
setStatus.accept(fallback, "Error");
return Mono.just(fallback);
});
}
- Then, you can use this method like that:
Mono<MailProvidersDto> mails = withRecovery(
partnersAsyncService.asyncCallPartnerApi(getMailboxProvidersUrl, MailboxProvidersDTO.class),
MailProvidersDto::setStatus,
MailProvidersDto::new
);
Mono<TimeZoneDto> timezone = withRecoery(
partnersAsyncService.asyncCallPartnerApi(getMailboxProvidersUrl, TimeZoneDto.class),
TimeZoneDto::setStatus,
TimeZoneDto::new
);
... // Repeat for each api
Notes:
- If the
setStatus
method is available through a common interface that all DTO implement, you can get rid of the biconsumer, and directly callresult.setStatus(String)
, by specializingT
generic toT extends StatusInterface
. - With it, you could also factorize initial fetching and retry calls, by passing related parameters (url, class, retry spec) as method input.