Reactor Samples - redutan/redutan.github.io GitHub Wiki
Flux<PodCast> ์์ ๊ฐ PodCast ๋ณ๋ก ๋ค์ ์กฐํ(api)ํ ํ ์กฐ๊ฑด(์ค๋์ค ์ฑ๋์ธ๊ฐ?)์ ๋ฐ๋ผ์ ํํฐ๋ง
// ๋ณ๋ ฌ์ฒ๋ฆฌ = flatMapSequential + callable + scheduler
private List<PodCast> filterAudioPodtyCasts(List<PodCast> casts) {
return Flux.fromIterable(casts)
.flatMapSequential(cast -> monoPodCastAndAudio(cast))
.filter(PodCastAndAudio::isAudio)
.map(PodtyCastAndAudio::getCast)
.collectList()
.block(Duration.ofSeconds(13));
}
private Mono<PodCastAndAudio> monoPodCastAndAudio(PodCast cast) {
Long castId = cast.getCastId();
return Mono.fromCallable(() -> audioChannelChecker.isAudio(castId))
.subscribeOn(Schedulers.elastic())
.map(isAudio -> new PodCastAndAudio(cast, isAudio));
}ํ๊ทธ, ํฌ์คํธ, ๋ด์ค, ๋ญํน ๋ฑ ๋ฉ์ธํ๋ฉด์ ๋ ธ์ถ๋๋ ์ฌ๋ฌ๊ฐ์ง API๋ฅผ ํ ๋ฒ์ ํธ์ถํด์ ์กฐํฉ
// ๋ณ๋ ฌ์ฒ๋ฆฌ = zip + TupleUtils.function & defer + scheduler(immediate)
return Mono.zip(tags(), posts(accessMemberId), news(), rankings(accessMemberId), curators(accessMemberId))
.map(TupleUtils.function(MainDetail::new))
.doOnError(this::logError)
.onErrorReturn(MainDetail.EMPTY)
// ์์ธ๋ฅผ ์ํ์ง ์๋๋ค๋ฉด .timeout ๋ ์ข์ ๋ฐฉ๋ฒ
.blockOptional(Duration.ofMillis(5000))
.orElse(MainDetail.EMPTY);
//...
private Mono<List<RecommendTagRow>> tags() {
return Mono.fromCallable(() -> recommendTagQuerier.getRecommendTags())
.subscribeOn(Schedulers.elastic())
.doOnError(this::logError)
.onErrorReturn(emptyList());
}@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class MainDetail {
public static final MainDetail EMPTY = new MainDetail(emptyList(),
emptyList(),
NewsStandDto.EMPTY,
emptyList(),
emptyList());
private List<RecommendTagRow> tags;
private List<PostDetail> posts;
private NewsStandDto news;
private List<PostDetail> rankings;
private List<RecommendCuratorRow> curators; private void setLatestReferenceDatas(Wiki wiki, PageContentReferencesHolder holder) {
// block ์์ด ๋น๋๊ธฐ์ ์ผ๋ก ๋ฐ์ดํฐ๋ค๋ฅผ ํ ๋ฒ์ ์กฐํ. ๋จ, ๋ชจ๋ ์ ๋ณด๋ฅผ ๋ค ์กฐํํ ๋ ๊น์ง block ํ๋ค.
Mono.zip(getMemberMap(wiki, holder), // member
getMemberGroupMap(wiki, holder)) // memberGroup
.blockOptional()
.ifPresent(TupleUtils.consumer(holder::setAllDatas));
}
Mono<Map<Long, TenantMember>> getMemberMap(Wiki wiki, PageContentReferencesHolder holder) {
if (!holder.hasReferences(PageContentReference.MEMBER)) {
return Mono.just(emptyMap());
}
Set<Long> orgMemberIds = holder.ids(PageContentReference.MEMBER);
return Mono.fromCallable(() -> accountService.getTenantMemberMap(wiki.getTenantId(), orgMemberIds))
.subscribeOn(Schedulers.elastic());
}
class PageContentReferencesHolder {
void setAllDatas(Map<Long, TenantMember> memberMap, Map<Long, ProjectMemberGroup> memberGroupMap) {
this.memberMap = memberMap;
this.memberGroupMap = memberGroupMap;
}
// ...
}ํธ์๋ฉ์์ง๋ฅผ ๋ฐ์กํ ํ ํธ์๋ฉ์์ง๊ฐ ์ ์์ ์ผ๋ก ๋ฐ์ก๋์๋์ง ํ์ธ. ๋ง์ฝ ํธ์๊ฐ ์ ์์ ์ผ๋ก ๋ฐ์ก๋์ง ์์์ผ๋ฉด ํธ์์คํจ ๋ฐํ
- Thread.sleep ์์ด ์ฒ๋ฆฌ ๊ฐ๋ฅ
Long messageId = messageIdGetter.getMessageId();
PushMessageResponse response =
Mono.just(messageId)
.map(this::getMessageByApi)
.retryWhen(
Retry.anyOf(WaitingException.class) // ์ด ์์ธ๊ฐ ์์ผ๋ฉด retry
.fixedBackoff(Duration.ofMillis(gapMillis)) // gapMillis ๊ฐ๊ฒฉ์ผ๋ก
.retryMax(retryMax) // retryMax ๋งํผ retry
.doOnRetry(this::logRetry))
.doOnError(this::logError)
.onErrorReturn(PushMessageResponse.EMPTY)
.blockOptional()
.orElse(PushMessageResponse.EMPTY);
if (FAIL == response.getStatus()) {
pushFailNotificator.send(new ContactMessagePushFail(message));
}
//...
private void logError(Throwable error) {
if (log.isWarnEnabled()) {
log.warn("PushFailCallbackPostExecutor Error : " + error.getMessage(), error);
}
}
private void logRetry(RetryContext<Object> context) {
if (log.isInfoEnabled()) {
log.info("Retry {} times. exception = {}", context.iteration(), context.exception().getMessage());
}
}
private PushMessageResponse getMessageByApi(Long messageId) {
PushMessageResponse response = client.getMessage(messageId);
if (WAIT == response.getStatus()) {
throw new WaitingException(); // ์กฐ๊ฑด์ ๋ง์กฑํ์ง ๋ชปํ๋ ๊ฒฝ์ฐ ํน์ ์์ธ ๋ฐ์(๊ทธ๋์ผ์ง retry๋จ)
}
return response;
}public class PageEventAssembler implements AutoCloseable {
...
private final Scheduler rowScheduler = Schedulers.newElastic("page-event-row");
...
private Mono<PageEventRow> monoPageEventRow(Tenant tenant, PageEvent source, OrganizationMember member) {
return Mono.fromCallable(() -> {
PageEventRow.Body body = toBody(tenant, source, member);
PageEventRow.Creator creator = toCreator(tenant, source);
return new PageEventRow(source.getEventId(), source.getPageId(), body,
ZonedDateTime.of(source.getCreatedAt(), member.getZone()), creator);
})
.doOnError(this::logError)
.onErrorReturn(PageEventRow.EMPTY)
// .subscribeOn(Schedulers.newElasitc("page-event-row")); <- ์ด๊ฑด ์ฐ๋ ๋ ๋ฌดํ ์ฆ๊ฐ ์ค๋ฅ๊ฐ ๋ฐ์ํ๊ฒ๋จ
.subscribeOn(rowScheduler);
}
@Override
public void close() {
if (rowScheduler.isDisposed()) {
return;
}
rowScheduler.dispose();
}
}