不同类型的CompletableFuture列表的最佳检索方法

huangapple go评论74阅读模式
英文:

Best practices to retrieve CompletableFuture lists of different types

问题

import org.springframework.scheduling.annotation.Async;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;

// Service class
@Service
public class Service {
    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 class
public class Controller {
    @GetMapping(value = "/parts/metadata", produces = { MediaType.APPLICATION_JSON_VALUE })
    public ResponseEntity<MetadataResponse> metadata(@RequestParam(required = false) String prefix,
                                                     @RequestParam String base,
                                                     @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 class
public class MetadataResponse extends BaseBodyResponse<MetadataResponse.MetadataResponseResult> {
    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public static class MetadataResponseResult {
        List<PartA> partAs;
        List<PartB> partBs;
        List<PartC> partCs;
    }
}
英文:

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&lt;PartA&gt; partAs,final List&lt;PartB&gt; partBs,final List&lt;PartC&gt; partCs,
String prefix,String base,String suffix) throws Exception {
try {
CompletableFuture&lt;List&lt;PartA&gt;&gt; futurePartAs = partACompletableFuture(prefix,base,suffix).thenApply(list -&gt; {
logger.info(&quot;PartA here&quot;);
return list;
});
CompletableFuture&lt;List&lt;PartB&gt;&gt; futurePartBs = partBCompletableFuture(prefix,base,suffix).thenApply(list -&gt; {
logger.info(&quot;PartBs here&quot;);
return list;
});
CompletableFuture&lt;List&lt;PartC&gt;&gt; futurePartCs = partCCompletableFuture(prefix,base,suffix).thenApply(list -&gt; {
logger.info(&quot;PartCs here&quot;);
return list;
});
CompletableFuture&lt;?&gt; 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(&quot;Exception: &quot;, e);
throw e;
}
}
@Async(&quot;asyncExecutor&quot;)
public CompletableFuture&lt;List&lt;PartA&gt;&gt; partACompletableFuture(String prefix,String base,String suffix) {
return CompletableFuture.supplyAsync(() -&gt; {
try {
logger.info(&quot;start PartA&quot;);
return getPartAs(prefix,base,suffix);
} catch (Exception e) {
logger.error(&quot;Exception: &quot;, e);
throw e;
}
});
}
@Async(&quot;asyncExecutor&quot;)
public CompletableFuture&lt;List&lt;PartB&gt;&gt; partBCompletableFuture(String prefix,String base,String suffix) {
return CompletableFuture.supplyAsync(() -&gt; {
try {
logger.info(&quot;start B&quot;);
return getPartBs(prefix,base,suffix);
} catch (Exception e) {
logger.error(&quot;Exception: &quot;, e);
throw e;
}
});
}
@Async(&quot;asyncExecutor&quot;)
public CompletableFuture&lt;List&lt;PartC&gt;&gt; partCCompletableFuture(String prefix,String base,String suffix) {
return CompletableFuture.supplyAsync(() -&gt; {
try {
logger.info(&quot;start PartC&quot;);
return getPartCs(prefix,base,suffix);
} catch (Exception e) {
logger.error(&quot;Exception: &quot;, e);
throw e;
}
});
}

In case you wish to view the Controller and Response type:

Controller.java

    @GetMapping(value=&quot;/parts/metadata&quot;,produces = { MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity&lt;MetadataResponse&gt; metadata (@ApiParam(name=&quot;prefix&quot;,value = &quot;Prefix value for a part&quot;,required = false)
@RequestParam(required=false) String prefix,
@ApiParam(name=&quot;base&quot;,value = &quot;Base value for a part&quot;,required= true)
@RequestParam String base,
@ApiParam(name=&quot;suffix&quot;,value = &quot;Suffix value for a part&quot;,required=false)
@RequestParam(required=false) @NotBlank  String suffix ) throws Exception {
final List&lt;PartA&gt; partAs = new ArrayList&lt;&gt;();
final List&lt;PartB&gt; partBs = new ArrayList&lt;&gt;();
final List&lt;PartC&gt; partCs = new ArrayList&lt;&gt;();
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 = &quot;MetadataResponse&quot;, parent = BaseBodyResponse.class, description = &quot;Part A, B, C&quot;)
public class MetadataResponse extends BaseBodyResponse&lt;MetadataResponse.MetadataResponseResult&gt; {
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ApiModel(value = &quot;MetadataResponseResult&quot;, description = &quot;This Model holds Part As, Bs, Cs&quot;)
public static class MetadataResponseResult {
List&lt;PartA&gt; partAs;
List&lt;PartB&gt; partBs;
List&lt;PartC&gt; partCs;
}
}

答案1

得分: 1

  • 我不完全理解为什么在这种情况下你需要将所有这些列表作为参数传递:public void metadata(final List&lt;PartA&gt; partAs,final List&lt;PartB&gt; partBs,final List&lt;PartC&gt; partCs, String prefix,String base,String suffix) throws Exception。你可以修改这个方法,让它返回你已经拥有的MetadataResponseResult类,并直接从ComparableFutures中使用这些列表。
  • 我会移除thenApply方法,因为你只是记录一条语句,实际上并没有改变结果。
  • 你可以有一个方法,接收一个Supplier作为参数,代替这三个方法(partACompletableFuturepartABCompletableFuturepartCCompletableFuture)。

以下是方法的示例代码:

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

然后你可以这样使用它:

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

这样会更清晰。希望对你有帮助!

英文:
  • I don't understand exactly why you need to pass all these lists as parameters in this case: public void metadata(final List&lt;PartA&gt; partAs,final List&lt;PartB&gt; partBs,final List&lt;PartC&gt; 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 directly
  • I would remove the thenApply methods since you just log a statement and you don't actually change the results.
  • Instead of having the three methods (partACompletableFuture, partABCompletableFuture, partCCompletableFuture) you could have one method that receives a Supplier as a parameter.
        @Async(&quot;asyncExecutor&quot;)
public &lt;T&gt; CompletableFuture&lt;T&gt; partCompletableFuture(Supplier&lt;T&gt; supplier) {
return CompletableFuture.supplyAsync(() -&gt; {
try {
logger.info(&quot;start Part&quot;);
return supplier.get();
} catch (Exception e) {
logger.error(&quot;Exception: &quot;, e);
throw e;
}
});
}

Aftewards you can use it as so:

CompletableFuture&lt;List&lt;PartA&gt;&gt; futurePartAs = partCompletableFuture(() -&gt; 
getPartAs(prefix,base,suffix));

It should much cleaner. Hope this helped!

huangapple
  • 本文由 发表于 2020年8月20日 20:17:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/63504925.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定