Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/haeun step3 #10

Open
wants to merge 15 commits into
base: base/haeun
Choose a base branch
from
Open

Feature/haeun step3 #10

wants to merge 15 commits into from

Conversation

pear96
Copy link

@pear96 pear96 commented Feb 16, 2024

구현 내용

[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는 하위 폴더들을 조회합니다.

[FileService]

  • 파일 이동시, 해당 파일의 folderId 값만 변경합니다.
  • 파일 삭제시 물리적 삭제 코드를 제거하고 삭제 로그(DeleteLog) 데이터를 생성하도록 수정했습니다.
  • 기존의 메서드를 역할에 맞게 더 작게 분리했습니다.

- 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]
- 불필요한 변수를 삭제했습니다.
Copy link
Member

@VSFe VSFe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

잘했네요?
신기

*/
@GetMapping("/subFolder")
@ResponseStatus(HttpStatus.OK)
public List<FolderMetaDataRes> getSubFolders(long folderId, Pageable pageable) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

folderId는 어디서 주입받나요?

* @param req (폴더 기본키, 이동할 폴더 기본키, 사용자 이름)
*/
@PatchMapping
@ResponseStatus(HttpStatus.MOVED_PERMANENTLY)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MOVED_PERMANENTLY는 리다이렉트를 위한 HttpStatusCode입니다.

@@ -7,7 +7,7 @@
* @see com.c4cometrue.mystorage.entity.FileMetaData
*/
public record FileReq(
@NotBlank(message = "file storage name is blank") String fileStorageName,
@NotNull(message = "file id is blank") long fileId,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

전체적인 로직을 고려했을 때, @Positive가 좀 더 자연스러워보여요.


import com.c4cometrue.mystorage.entity.FolderMetaData;

public interface FolderRepository extends JpaRepository<FolderMetaData, Long> {
Optional<FolderMetaData> findByFolderId(long folderId);

@Query("SELECT f.parentFolderId FROM FolderMetaData f where f.folderId=:folderId")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

인덱스 확인 해 주세요.

* @param userName 사용자 이름
* @return 파일 메타 데이터
*/
FileMetaData getFileMetaData(long fileId, String userName) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private인가요? public 인가요?

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));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

50은 상수로 빼면 좋을 것 같네요.

ArrayDeque<Long> folderQueue = new ArrayDeque<>();
folderQueue.add(folderId);

while (!folderQueue.isEmpty()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+수정할 필요는 없습니다. 간단한 제안?

  • 굳이 폴더 하나하나에 대해서 단건으로 할 필요가 없어보여요.
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*이 부분도 바로 수정할 필요는 없습니다.

하위 파일의 수가 너무 많아지면, 데이터를 가져오는 것도, 그 것을 기반으로 쿼리 생성을 하는 것도 상당히 부담스러울 수 있습니다.
(파일의 수가 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는 상수로 추출했습니다.
Copy link

sonarcloud bot commented Mar 1, 2024

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants