Рекомендации по извлечению завершаемых списков будущего разных типов

#java #spring #asynchronous #completable-future

#java #spring #асинхронный #завершаемый-будущее

Вопрос:

Я хочу извлекать данные разных типов из базы данных и возвращать пользователю в результате HTTP из службы загрузки Spring. Поскольку извлечение базы данных занимает значительное количество времени для каждого, я выполняю эти вызовы БД асинхронно с CompletableFuture. Шаблон, который у меня есть, работает и экономит время по сравнению с выполнением этого синхронно, но я чувствую, что его можно и нужно изложить более чистым способом.

Я отредактировал код, чтобы изменить типы на ‘PartA’, ‘PartB’, ‘PartC’, но это выглядит иначе. В настоящий момент сервис принимает списки разных типов (парта, PartB, PartC), создает завершенный будущем видах каждого списка вызова собственного метода CompletableFuture, который вызывает базу данных, строит родовое список CompleteableFutures с каждого типа, «получает» общий список, а затем добавляет все содержимое каждой будущей список список перешедших на службу.

Вот как кодируются методы службы:

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;
            }
        });
    }
  

В случае, если вы хотите просмотреть контроллер и тип ответа:

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;
    }

}
  

Ответ №1:

  • Я не совсем понимаю, зачем вам нужно передавать все эти списки в качестве параметров в этом случае: public void metadata(final List<PartA> partAs,final List<PartB> partBs,final List<PartC> partCs, String prefix,String base,String suffix) throws Exception Вы могли бы изменить этот метод, чтобы вернуть MetadataResponseResult класс, который у вас уже есть, и использовать списки из ComparableFutures напрямую
  • Я бы удалил thenApply методы, поскольку вы просто регистрируете инструкцию и фактически не изменяете результаты.
  • Вместо трех методов ( partACompletableFuture , partABCompletableFuture partCCompletableFuture ) у вас мог бы быть один метод, который получает поставщика в качестве параметра.
         @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;
                }
            });
        }
  

Впоследствии вы можете использовать его как so:

 CompletableFuture<List<PartA>> futurePartAs = partCompletableFuture(() -> 
                                     getPartAs(prefix,base,suffix));
  

Это должно быть намного чище. Надеюсь, это помогло!