Conversation
- file과 folder의 DTO를 폴더로 분리했습니다.
[FileService] - 파일 이동시, 해당 파일의 `folderId` 값만 변경합니다. - 파일 삭제시 물리적 삭제 코드를 제거하고 삭제 로그(DeleteLog) 데이터를 생성하도록 수정했습니다. - 기존의 메서드를 역할에 맞게 더 작게 분리했습니다. [FileMetaData] - 파일의 폴더 기본키를 수정하는 setter를 추가했습니다. [FileReq] - 파일 업로드, 다운로드, 삭제시 파일의 '저장소명' 대신 '기본키'를 사용하도록 수정했습니다.
[FolderService]
- 폴더 삭제는 BFS로 진행합니다.
1. 삭제 요청받은 해당 폴더를 삭제합니다.
2. BFS로 해당 폴더의 모든 하위폴더들을 조회하도록 합니다.
3. 현재 삭제할 차례인 폴더의 모든 파일을 지웁니다.
3-1. `folderId`를 해당 폴더 기본키로 갖는 모든 파일을 찾습니다.
3-2. `DeleteLog`에 삭제할 파일들을 기록합니다.
3-3. 해당 파일들을 `deleteAllInBatch()`로 `FileMetaData`에서 전부 제거합니다.
4. 해당 폴더의 기본키를 `parentFolderId`로 갖는 모든 폴더를 조회합니다.
5. `deleteAllInBatch()`로 하위 폴더들을 DB에서 제거하고, `folderQueue`에 추가합니다.
- 폴더 이동시 상위 폴더가 하위 폴더의 밑으로 이동할 수 없도록 방지하는 로직을 구현했습니다.
- `checkFolderRelationship(folderId, targetFolderId)`는 `targetFolderId`(이동할 목적지)의 부모에 `folderId`(이동하려는 주체)가 있는지 검사합니다.
- 이동하려는 폴더의 자녀에 목적지가 있는지 검사하는 것보다, 목적지의 부모에 이동하려는 폴더가 존재하는지 검사하는 것이 더 빠르다고 생각했습니다.
- 폴더를 조회할 때 해당 폴더의 하위 폴더, 파일들을 조회할 때 대량의 데이터를 한번에 조회하는 위험을 방지하고자 Page를 적용했습니다.
- Page의 크기는 50으로 설정했습니다.
- `getFiles`는 하위 파일들을 ,`getFolders`는 하위 폴더들을 조회합니다.
- 폴더 전체 조회하는 메서드의 이름을 `getFolderTotalInfo`로 수정했습니다. 기존의 `getFolderData`폴더 메타 데이터를 조회하는 `getFolderInfo`와 구별하기 어렵다고 생각했습니다.
[FolderMetaData]
- 폴더의 부모 폴더 기본키를 수정하는 setter를 추가했습니다.
[FolderController]
- 특정 폴더의 하위 폴더, 파일들을 page 단위로 조회할 수 있는 API를 추가했습니다.
[FolderRepository]
- 폴더 삭제 과정에서 폴더의 기본키로 BFS의 Queue를 사용했기 때문에 `parentFolderId`만 조회하는 JPQL을 작성했습니다.
[DeleteLog] - 삭제할 파일의 저장소 명과 삭제를 요청한 날짜를 저장합니다. - 삭제 날짜는 한국 서버 시간 기준으로 통일하도록 했습니다.
[CreateFolderRes] - 폴더를 생성한 사용자 명을 반환할 필요가 없어서 제거했습니다. [ErrorCd] - 폴더 이동시 사이클이 생기는 경우 예외를 추가했습니다. [FileServiceTest] - 불필요한 변수를 삭제했습니다.
| */ | ||
| @GetMapping("/subFolder") | ||
| @ResponseStatus(HttpStatus.OK) | ||
| public List<FolderMetaDataRes> getSubFolders(long folderId, Pageable pageable) { |
| * @param req (폴더 기본키, 이동할 폴더 기본키, 사용자 이름) | ||
| */ | ||
| @PatchMapping | ||
| @ResponseStatus(HttpStatus.MOVED_PERMANENTLY) |
There was a problem hiding this comment.
MOVED_PERMANENTLY는 리다이렉트를 위한 HttpStatusCode입니다.
| */ | ||
| public record FileReq( | ||
| @NotBlank(message = "file storage name is blank") String fileStorageName, | ||
| @NotNull(message = "file id is blank") long fileId, |
There was a problem hiding this comment.
전체적인 로직을 고려했을 때, @Positive가 좀 더 자연스러워보여요.
| public interface FolderRepository extends JpaRepository<FolderMetaData, Long> { | ||
| Optional<FolderMetaData> findByFolderId(long folderId); | ||
|
|
||
| @Query("SELECT f.parentFolderId FROM FolderMetaData f where f.folderId=:folderId") |
| * @param userName 사용자 이름 | ||
| * @return 파일 메타 데이터 | ||
| */ | ||
| FileMetaData getFileMetaData(long fileId, String userName) { |
| private List<FileMetaDataRes> getFiles(long folderId) { | ||
| Optional<List<FileMetaData>> fileList = fileRepository.findAllByFolderId(folderId); | ||
| public List<FileMetaDataRes> getFiles(long folderId, int page) { | ||
| Page<FileMetaData> filePage = fileRepository.findAllByFolderId(folderId, PageRequest.of(page, 50)); |
There was a problem hiding this comment.
OffsetBasedPaging 을 하고 있는 것 같은데, 파일의 수가 많아져도 괜찮을 지 생각해보면 좋을 것 같아요.
(실제 드라이브에서 파일을 어떤식으로 제공하고 있는지 확인해보면 좀 더 잘 와닿을 것 같아요.)
| private List<FileMetaDataRes> getFiles(long folderId) { | ||
| Optional<List<FileMetaData>> fileList = fileRepository.findAllByFolderId(folderId); | ||
| public List<FileMetaDataRes> getFiles(long folderId, int page) { | ||
| Page<FileMetaData> filePage = fileRepository.findAllByFolderId(folderId, PageRequest.of(page, 50)); |
| ArrayDeque<Long> folderQueue = new ArrayDeque<>(); | ||
| folderQueue.add(folderId); | ||
|
|
||
| while (!folderQueue.isEmpty()) { |
There was a problem hiding this comment.
+수정할 필요는 없습니다. 간단한 제안?
- 굳이 폴더 하나하나에 대해서 단건으로 할 필요가 없어보여요.
select * from file_info where parent_id in (1, 2, 3, 4, 5, 6, 7);과 같은 방식으로 하면, Queue에 데이터가 여러개에도 문제 없이 가능할 것 같아요.
이 경우 쿼리의 성능도 매우 좋아지고, 전체적인 처리 성능이 향상되겠죠?
다만, 이 경우 하위 폴더의 수가 너무 많아지면 끊어서 처리를 해야하기에, 다소 구현이 조금 빡빡해집니다.
| */ | ||
| @Transactional | ||
| public void deleteAllFile(long folderId) { | ||
| Optional<List<FileMetaData>> fileList = fileRepository.findAllByFolderId(folderId); |
There was a problem hiding this comment.
*이 부분도 바로 수정할 필요는 없습니다.
하위 파일의 수가 너무 많아지면, 데이터를 가져오는 것도, 그 것을 기반으로 쿼리 생성을 하는 것도 상당히 부담스러울 수 있습니다.
(파일의 수가 2000개 정도라고 하면, 그걸 기반으로 delete 쿼리를 만드는 것도 썩 좋지는... 못해요.)
- `long id`(파일, 폴더 등) 에 대해 `@NotNull` 유효성을 설정했던 것에서 `@Positive`로 변경했습니다. - 변경해야하는 이유는 우선 `long`이기 때문에 어차피 null 값을 사용할 수 없습니다. - 또한 -1 과 같이 정상적이지 않은 경우를 처리할 수 없습니다.
- 폴더의 하위 폴더, 파일 목록 조회시 필요한 값을 전달받는 DTO를 추가했습니다. - Pageable에서 페이지 번호만 사용하고 있었기 때문에 Pageable의 객체는 서비스 레이어에서 생성하고, 페이지 번호만 클라이언트에서 전달받도록 수정했습니다. - 폴더 이동시 응답 코드를 200으로 수정했습니다.
- DTO에 설정한 각 필드의 유효성을 통과하지 못할 경우 400 에러를 반환하도록 핸들러를 추가했습니다. - 유효성을 어겼을 시 기존에 설정해둔 메세지가 log에 나오도록 하고, 클라이언트에는 통합된 메세지를 반환하도록 했습니다.
- `getFileMetaData()`메서드가 `private` 접근자를 설정해야하는데, 테스트 코드를 작성할 수 없어 default로 설정했었습니다. - `public`으로 바꾸는 것은 FileService 외부에서 사용하지 않기 때문에 적합하지 않다고 생각했습니다. - 따라서 `private`으로 바꾸고, `getFileMetaData()`를 호출하는 public 메서드들에 대해 실패 테스트 케이스 코드를 전부 추가했습니다.
- Pageable을 Client에서 전달받지 않고, pageNumber에 따라 PageRequest 객체를 생성합니다. - 이때 pageSize는 상수로 추출했습니다.
|



구현 내용
[FolderService] - 폴더의 삭제와 이동
3-1.
folderId를 해당 폴더 기본키로 갖는 모든 파일을 찾습니다.3-2.
DeleteLog에 삭제할 파일들을 기록합니다.3-3. 해당 파일들을
deleteAllInBatch()로FileMetaData에서 전부 제거합니다.parentFolderId로 갖는 모든 폴더를 조회합니다.deleteAllInBatch()로 하위 폴더들을 DB에서 제거하고,folderQueue에 추가합니다.checkFolderRelationship(folderId, targetFolderId)는targetFolderId(이동할 목적지)의 부모에folderId(이동하려는 주체)가 있는지 검사합니다.getFiles는 하위 파일들을 ,getFolders는 하위 폴더들을 조회합니다.[FileService]
folderId값만 변경합니다.