Reputation: 127
I want to retrieve data of different types from a database and return to the user within an HTTP result from a Spring Boot service. Because the database retrieval takes a significant amount of time for each, I am making these DB calls asynchronously with CompletableFuture. The pattern I have works and saves time compared to doing this synchronously, but I feel that it can and should be laid out in a cleaner fashion.
I edited the code to change the types to 'PartA', 'PartB', 'PartC', but this is otherwise how it appears. Currently, the service accepts the lists of different types (PartA, PartB, PartC), creates Completable future types of each list calling its own CompletableFuture method that calls the DB, builds a generic list of CompleteableFutures with each type, "gets" the generic list, then adds all the contents of each Future list to the list passed into the service.
This is how the Service methods are coded:
Service.java:
public void metadata(final List<PartA> partAs,final List<PartB> partBs,final List<PartC> partCs,
String prefix,String base,String suffix) throws Exception {
try {
CompletableFuture<List<PartA>> futurePartAs = partACompletableFuture(prefix,base,suffix).thenApply(list -> {
logger.info("PartA here");
return list;
});
CompletableFuture<List<PartB>> futurePartBs = partBCompletableFuture(prefix,base,suffix).thenApply(list -> {
logger.info("PartBs here");
return list;
});
CompletableFuture<List<PartC>> futurePartCs = partCCompletableFuture(prefix,base,suffix).thenApply(list -> {
logger.info("PartCs here");
return list;
});
CompletableFuture<?> combinedFuture = CompletableFuture.allOf(CompletableFuture.allOf(futurePartAs, futurePartBs, futurePartCs));
combinedFuture.get();
partAs.addAll(futurePartAs.get());
partBs.addAll(futurePartBs.get());
partCs.addAll(futurePartCs.get());
} catch (Exception e) {
logger.error("Exception: ", e);
throw e;
}
}
@Async("asyncExecutor")
public CompletableFuture<List<PartA>> partACompletableFuture(String prefix,String base,String suffix) {
return CompletableFuture.supplyAsync(() -> {
try {
logger.info("start PartA");
return getPartAs(prefix,base,suffix);
} catch (Exception e) {
logger.error("Exception: ", e);
throw e;
}
});
}
@Async("asyncExecutor")
public CompletableFuture<List<PartB>> partBCompletableFuture(String prefix,String base,String suffix) {
return CompletableFuture.supplyAsync(() -> {
try {
logger.info("start B");
return getPartBs(prefix,base,suffix);
} catch (Exception e) {
logger.error("Exception: ", e);
throw e;
}
});
}
@Async("asyncExecutor")
public CompletableFuture<List<PartC>> partCCompletableFuture(String prefix,String base,String suffix) {
return CompletableFuture.supplyAsync(() -> {
try {
logger.info("start PartC");
return getPartCs(prefix,base,suffix);
} catch (Exception e) {
logger.error("Exception: ", e);
throw e;
}
});
}
In case you wish to view the Controller and Response type:
Controller.java
@GetMapping(value="/parts/metadata",produces = { MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<MetadataResponse> metadata (@ApiParam(name="prefix",value = "Prefix value for a part",required = false)
@RequestParam(required=false) String prefix,
@ApiParam(name="base",value = "Base value for a part",required= true)
@RequestParam String base,
@ApiParam(name="suffix",value = "Suffix value for a part",required=false)
@RequestParam(required=false) @NotBlank String suffix ) throws Exception {
final List<PartA> partAs = new ArrayList<>();
final List<PartB> partBs = new ArrayList<>();
final List<PartC> partCs = new ArrayList<>();
service.metadata(partAs,partBs,partCs,prefix,base,suffix);
MetadataResponse.MetadataResponseResult res = MetadataResponse.MetadataResponseResult.builder()
.partAs(partAs)
.partBs(partBs)
.partCs(partCs)
.build();
return ResponseEntity.ok(MetadataResponse.result(res, MetadataResponse.class));
}
MetadataResponse.java
@ApiModel(value = "MetadataResponse", parent = BaseBodyResponse.class, description = "Part A, B, C")
public class MetadataResponse extends BaseBodyResponse<MetadataResponse.MetadataResponseResult> {
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ApiModel(value = "MetadataResponseResult", description = "This Model holds Part As, Bs, Cs")
public static class MetadataResponseResult {
List<PartA> partAs;
List<PartB> partBs;
List<PartC> partCs;
}
}
Upvotes: 0
Views: 1538
Reputation: 21
public void metadata(final List<PartA> partAs,final List<PartB> partBs,final List<PartC> partCs, String prefix,String base,String suffix) throws Exception
You could modify this method to return the MetadataResponseResult
class you already have and use the lists from the ComparableFutures directlythenApply
methods since you just log a statement and you don't actually change the results.partACompletableFuture
, partABCompletableFuture
, partCCompletableFuture
) you could have one method that receives a Supplier as a parameter. @Async("asyncExecutor")
public <T> CompletableFuture<T> partCompletableFuture(Supplier<T> supplier) {
return CompletableFuture.supplyAsync(() -> {
try {
logger.info("start Part");
return supplier.get();
} catch (Exception e) {
logger.error("Exception: ", e);
throw e;
}
});
}
Aftewards you can use it as so:
CompletableFuture<List<PartA>> futurePartAs = partCompletableFuture(() ->
getPartAs(prefix,base,suffix));
It should much cleaner. Hope this helped!
Upvotes: 1