Skip to content

Commit

Permalink
[refactor] 주차별 첨부파일 업로드를 비동기 + 병렬로 처리하도록 개선한다 (#30)
Browse files Browse the repository at this point in the history
* chore: 파일 업로드 관련 용량 제한 설정

* feat: 주차별 첨부파일 업로드 `@Size` 10개 제한

* feat: 첨부파일 업로드 CompletableFuture + 커스텀 ThreadPool 적용

* test: 첨부파일 단위 테스트 관련 CompletableFuture로 인한 Asynchronous 무한 대기 -> `@SpringBootTest`로 수정
  • Loading branch information
sjiwon authored Dec 18, 2023
1 parent 7e29838 commit 7f453d7
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
@EnableAsync
public class AsyncConfiguration implements AsyncConfigurer {
@Bean(name = "emailAsyncExecutor")
public Executor getAsyncExecutor() {
public Executor emailAsyncExecutor() {
final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(40);
Expand All @@ -27,6 +27,18 @@ public Executor getAsyncExecutor() {
return executor;
}

@Bean(name = "fileUploadExecutor")
public Executor fileUploadExecutor() {
final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(40);
executor.setQueueCapacity(50);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setAwaitTerminationSeconds(60);
executor.setThreadNamePrefix("Asynchronous File Upload Thread-");
return executor;
}

@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (ex, method, params) ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,41 @@
import com.kgu.studywithme.file.application.adapter.FileUploader;
import com.kgu.studywithme.file.domain.model.RawFileData;
import com.kgu.studywithme.studyweekly.domain.model.UploadAttachment;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

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

@Component
@RequiredArgsConstructor
public class AttachmentUploader {
private final FileUploader fileUploader;
private final Executor executor;

public AttachmentUploader(
final FileUploader fileUploader,
@Qualifier("fileUploadExecutor") final Executor executor
) {
this.fileUploader = fileUploader;
this.executor = executor;
}

public List<UploadAttachment> uploadAttachments(final List<RawFileData> files) {
if (CollectionUtils.isEmpty(files)) {
return List.of();
}

return files.stream()
.map(file -> new UploadAttachment(file.fileName(), fileUploader.uploadFile(file)))
final List<CompletableFuture<UploadAttachment>> result = files.stream()
.map(file -> CompletableFuture.supplyAsync(
() -> new UploadAttachment(file.fileName(), fileUploader.uploadFile(file)),
executor
))
.toList();

return result.stream()
.map(CompletableFuture::join)
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.multipart.MultipartFile;

Expand Down Expand Up @@ -29,6 +30,7 @@ public record CreateStudyWeeklyRequest(
@NotNull(message = "자동 출석 여부는 필수입니다.")
Boolean autoAttendance,

@Size(max = 10, message = "첨부파일은 최대 10개까지 등록할 수 있습니다.")
List<MultipartFile> files
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.multipart.MultipartFile;

Expand Down Expand Up @@ -29,6 +30,7 @@ public record UpdateStudyWeeklyRequest(
@NotNull(message = "자동 출석 여부는 필수입니다.")
Boolean autoAttendance,

@Size(max = 10, message = "첨부파일은 최대 10개까지 등록할 수 있습니다.")
List<MultipartFile> files
) {
}
5 changes: 5 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ spring:
starttls:
enable: true

servlet:
multipart:
max-file-size: 20MB
max-request-size: 30MB

oauth2:
google:
grant-type: authorization_code
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.kgu.studywithme.studyweekly.domain.service;

import com.kgu.studywithme.common.ParallelTest;
import com.kgu.studywithme.common.mock.stub.StubFileUploader;
import com.kgu.studywithme.file.application.adapter.FileUploader;
import com.kgu.studywithme.file.domain.model.RawFileData;
Expand All @@ -9,18 +8,44 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;

import java.io.IOException;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

import static com.kgu.studywithme.common.utils.FileMockingUtils.createMultipleMockMultipartFile;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;

/**
* CompletableFuture asynchronous로 인해 단위 테스트에서 무한 대기하는 현상 발생 <br>
* -> 임시 대안으로 @SpringBootTest + AttachmentUploader만 Context에 띄워서 진행
*/
@SpringBootTest(classes = {AttachmentUploader.class})
@Import(AttachmentUploaderTest.AttachmentUploaderTestConfiguration.class)
@DisplayName("StudyWeekly -> AttachmentUploader 테스트")
public class AttachmentUploaderTest extends ParallelTest {
private final FileUploader fileUploader = new StubFileUploader();
private final AttachmentUploader sut = new AttachmentUploader(fileUploader);
public class AttachmentUploaderTest {
@TestConfiguration
static class AttachmentUploaderTestConfiguration {
@Bean
public FileUploader fileUploader() {
return new StubFileUploader();
}

@Bean
public Executor fileUploadExecutor() {
return Executors.newFixedThreadPool(10);
}
}

@Autowired
private AttachmentUploader sut;

private List<RawFileData> files;

Expand Down
2 changes: 1 addition & 1 deletion study-with-me-secret

0 comments on commit 7f453d7

Please # to comment.