Skip to content

Commit

Permalink
refactor: async event
Browse files Browse the repository at this point in the history
refactor: async event
  • Loading branch information
parksey committed Apr 18, 2024
2 parents 4c0b205 + 40758c0 commit fde7b96
Show file tree
Hide file tree
Showing 21 changed files with 269 additions and 52 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package shop.jtoon.global.config;

import java.util.concurrent.Executor;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@Configuration
public class ApplicationEventConfig {

@Bean
public ApplicationEventMulticaster applicationEventMulticaster() {
SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster();
eventMulticaster.setTaskExecutor(asyncExecutor());
return eventMulticaster;
}

private Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(10000);
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(10);
executor.initialize();
return executor;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package shop.jtoon.global.util;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EventListener
public @interface AsyncEventListener {
Phase phase() default Phase.AFTER_COMMIT;

boolean fallbackExecution() default false;

@AliasFor(
annotation = EventListener.class,
attribute = "classes"
)
Class<?>[] value() default {};

@AliasFor(
annotation = EventListener.class,
attribute = "classes"
)
Class<?>[] classes() default {};

@AliasFor(
annotation = EventListener.class,
attribute = "condition"
)
String condition() default "";

@AliasFor(
annotation = EventListener.class,
attribute = "id"
)
String id() default "";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package shop.jtoon.global.util;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public enum Phase {
BEFORE_COMMIT,
AFTER_COMMIT,
AFTER_ROLLBACK,
AFTER_COMPLETION;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,23 @@

import java.util.List;

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import lombok.RequiredArgsConstructor;

import shop.jtoon.dto.ImageUploadEvent;
import shop.jtoon.dto.MultiImageEvent;
import shop.jtoon.exception.InvalidRequestException;

import shop.jtoon.webtoon.domain.EpisodeMainInfo;
import shop.jtoon.webtoon.domain.EpisodeSchema;
import shop.jtoon.webtoon.entity.Webtoon;
import shop.jtoon.webtoon.presentation.WebtoonImageUploadEventListener;
import shop.jtoon.webtoon.request.CreateEpisodeReq;
import shop.jtoon.webtoon.request.GetEpisodesReq;
import shop.jtoon.webtoon.request.MultiImagesReq;
import shop.jtoon.webtoon.response.EpisodeInfoRes;
import shop.jtoon.webtoon.response.EpisodeItemRes;
import shop.jtoon.webtoon.service.EpisodeDomainService;
Expand All @@ -27,30 +32,36 @@ public class EpisodeService {

private final WebtoonClientService webtoonClientService;
private final EpisodeDomainService episodeDomainService;
private final ApplicationEventPublisher publisher;

public void createEpisode(
Long memberId,
Long webtoonId,
MultipartFile mainImage,
MultiImagesReq mainImages,
MultipartFile thumbnailImage,
CreateEpisodeReq request
) {
Webtoon webtoon = episodeDomainService.readWebtoon(webtoonId, memberId, request.no());
String mainUrl = webtoonClientService.upload(request.toUploadImageDto(EPISODE_MAIN, webtoon.getTitle(), mainImage));
String thumbnailUrl = webtoonClientService.upload(request.toUploadImageDto(

MultiImageEvent mainUploadEvents = MultiImageEvent.builder()
.imageUploadEvents(mainImages.toMultiImageEvent(request, webtoon.getTitle()))
.build();
List<String> mainUrls = mainUploadEvents.imageUploadEvents().stream()
.map(webtoonClientService::uploadUrl)
.toList();

ImageUploadEvent thumbnailUploadEvent = request.toUploadImageDto(
EPISODE_THUMBNAIL,
webtoon.getTitle(),
thumbnailImage
));

try {
EpisodeSchema episode = request.toEpisodeSchema();
episodeDomainService.createEpisode(episode, webtoon, mainUrl, thumbnailUrl);
} catch (RuntimeException e) {
webtoonClientService.deleteImage(mainUrl);
webtoonClientService.deleteImage(thumbnailUrl);
throw new InvalidRequestException(EPISODE_CREATE_FAIL);
}
).toImageUploadEvent();
String thumbnailUrl = webtoonClientService.upload(thumbnailUploadEvent);

EpisodeSchema episode = request.toEpisodeSchema();
episodeDomainService.createEpisode(episode, webtoon, mainUrls, thumbnailUrl);

publisher.publishEvent(mainUploadEvents);
publisher.publishEvent(thumbnailUploadEvent);
}

public List<EpisodeItemRes> getEpisodes(Long webtoonId, GetEpisodesReq request) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
package shop.jtoon.webtoon.application;

import static shop.jtoon.common.ImageType.*;

import java.util.function.Function;
import java.util.function.Supplier;

import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import lombok.RequiredArgsConstructor;
import shop.jtoon.common.ImageType;
import shop.jtoon.dto.ImageUploadEvent;
import shop.jtoon.dto.UploadImageDto;
import shop.jtoon.service.S3Service;
import shop.jtoon.webtoon.request.CreateEpisodeReq;
import shop.jtoon.webtoon.request.CreateWebtoonReq;

@Service
Expand All @@ -27,8 +24,12 @@ public String upload(ImageType imageType, CreateWebtoonReq request, MultipartFil
return s3Service.uploadImage(uploadImageDto);
}

public String upload(UploadImageDto uploadImageDto) {
return s3Service.uploadImage(uploadImageDto);
public String uploadUrl(ImageUploadEvent imageUpload) {
return s3Service.uploadUrl(imageUpload.key());
}

public String upload(ImageUploadEvent imageUpload) {
return s3Service.uploadImage(imageUpload);
}

public void deleteImage(String thumbnailUrl) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
package shop.jtoon.webtoon.application;

import static shop.jtoon.common.ImageType.*;
import static shop.jtoon.type.ErrorStatus.*;

import java.util.List;
import java.util.Map;

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import lombok.RequiredArgsConstructor;
import shop.jtoon.dto.UploadImageDto;

import shop.jtoon.exception.InvalidRequestException;

import shop.jtoon.dto.ImageUploadEvent;
import shop.jtoon.webtoon.domain.WebtoonDetail;
import shop.jtoon.webtoon.entity.enums.DayOfWeek;
import shop.jtoon.webtoon.request.CreateWebtoonReq;
Expand All @@ -26,23 +23,22 @@
@RequiredArgsConstructor
public class WebtoonService {


private final WebtoonClientService webtoonClientService;
private final WebtoonDomainService webtoonDomainService;
private final ApplicationEventPublisher publisher;

public void createWebtoon(Long memberId, MultipartFile thumbnailImage, CreateWebtoonReq request) {
ImageUploadEvent imageUploadEvent = request.toUploadImageDto(WEBTOON_THUMBNAIL, thumbnailImage)
.toImageUploadEvent();
String thumbnailUrl = webtoonClientService.uploadUrl(imageUploadEvent);

webtoonDomainService.validateDuplicateTitle(request.title());
String thumbnailUrl = webtoonClientService.upload(request.toUploadImageDto(WEBTOON_THUMBNAIL, thumbnailImage));

try {
webtoonDomainService.createWebtoon(memberId,
request.toWebtoonInfo(thumbnailUrl),
request.toWebtoonGenres(),
request.toWebtoonDayOfWeeks());
} catch (RuntimeException e) {
webtoonClientService.deleteImage(thumbnailUrl);
throw new InvalidRequestException(WEBTOON_CREATE_FAIL);
}
webtoonDomainService.createWebtoon(memberId,
request.toWebtoonInfo(thumbnailUrl),
request.toWebtoonGenres(),
request.toWebtoonDayOfWeeks());

publisher.publishEvent(imageUploadEvent);
}

public Map<DayOfWeek, List<WebtoonItemRes>> getWebtoons(GetWebtoonsReq request) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import shop.jtoon.webtoon.request.CreateWebtoonReq;
import shop.jtoon.webtoon.request.GetEpisodesReq;
import shop.jtoon.webtoon.request.GetWebtoonsReq;
import shop.jtoon.webtoon.request.MultiImagesReq;
import shop.jtoon.webtoon.response.EpisodeInfoRes;
import shop.jtoon.webtoon.response.EpisodeItemRes;
import shop.jtoon.webtoon.response.WebtoonInfoRes;
Expand Down Expand Up @@ -52,11 +53,11 @@ public void createWebtoon(
public void createEpisode(
@CurrentUser MemberDto member,
@PathVariable Long webtoonId,
@RequestPart MultipartFile mainImage,
@RequestPart MultiImagesReq multiImagesReq,
@RequestPart MultipartFile thumbnailImage,
@RequestPart @Valid CreateEpisodeReq request
) {
episodeService.createEpisode(member.id(), webtoonId, mainImage, thumbnailImage, request);
episodeService.createEpisode(member.id(), webtoonId, multiImagesReq, thumbnailImage, request);
}

@GetMapping
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package shop.jtoon.webtoon.presentation;

import org.springframework.stereotype.Component;

import lombok.RequiredArgsConstructor;
import shop.jtoon.dto.ImageUploadEvent;
import shop.jtoon.dto.MultiImageEvent;
import shop.jtoon.global.util.AsyncEventListener;
import shop.jtoon.webtoon.application.WebtoonClientService;

@Component
@RequiredArgsConstructor
public class WebtoonImageUploadEventListener {

private final WebtoonClientService webtoonClientService;

@AsyncEventListener
public void uploadImage(ImageUploadEvent imageUploadEvent) {
webtoonClientService.upload(imageUploadEvent);
}

@AsyncEventListener
public void uploadMultiImages(MultiImageEvent multiImageEvent) {
multiImageEvent.imageUploadEvents().stream()
.parallel()
.forEach(webtoonClientService::upload);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import lombok.Builder;
import shop.jtoon.common.FileName;
import shop.jtoon.common.ImageType;
import shop.jtoon.dto.ImageUploadEvent;
import shop.jtoon.dto.UploadImageDto;
import shop.jtoon.webtoon.domain.EpisodeSchema;
import shop.jtoon.webtoon.entity.Episode;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package shop.jtoon.webtoon.request;

import static shop.jtoon.common.ImageType.*;

import java.util.List;

import org.springframework.web.multipart.MultipartFile;

import lombok.Builder;
import shop.jtoon.dto.ImageUploadEvent;

@Builder
public record MultiImagesReq(
List<MultipartFile> mainImages
) {

public List<ImageUploadEvent> toMultiImageEvent(CreateEpisodeReq request, String webtoonTitle) {
return mainImages.stream()
.map(mainImage -> request.toUploadImageDto(EPISODE_MAIN, webtoonTitle, mainImage).toImageUploadEvent())
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
package shop.jtoon.webtoon.response;

import java.util.List;

import lombok.Builder;
import shop.jtoon.webtoon.domain.EpisodeMainInfo;
import shop.jtoon.webtoon.entity.Episode;

@Builder
public record EpisodeInfoRes(
String mainUrl
List<String> mainUrls
) {

public static EpisodeInfoRes from(EpisodeMainInfo episode) {
return EpisodeInfoRes.builder()
.mainUrl(episode.mainUrl())
.mainUrls(episode.mainUrls())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package shop.jtoon.util;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class WebtoonUitls {

public static final String URL_SPLITER = ",";
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
package shop.jtoon.webtoon.domain;

import static shop.jtoon.util.WebtoonUitls.*;

import java.util.List;

import lombok.Builder;
import shop.jtoon.webtoon.entity.Episode;

@Builder
public record EpisodeMainInfo(
String mainUrl
List<String> mainUrls
) {

public static EpisodeMainInfo of(Episode episode) {
return EpisodeMainInfo.builder()
.mainUrl(episode.getMainUrl())
.mainUrls(List.of(episode.getMainUrl().split(URL_SPLITER)))
.build();
}
}
Loading

0 comments on commit fde7b96

Please sign in to comment.