Skip to content

Conversation

@leegwichan
Copy link
Member

@leegwichan leegwichan commented Jul 23, 2025

✨ 개요

  • ExternalImageStorage 의 책임을 나누기 위해 여러 객체 도입
    • 도메인 객체 : Image, ImageKey
    • 스토리지 관련 객체 : ImageStorage, ExternalImageStorage, CachePreSignedUrlStorage, FileClient

🧾 관련 이슈

closed #96

🔍 참고 사항 (선택)

Summary by CodeRabbit

  • 신규 기능

    • 이미지 파일 업로드 및 S3 프리사인 URL 생성을 위한 파일 클라이언트 도입
    • 이미지 도메인 객체(Image, ImageKey) 추가 및 이미지 유형·확장자 검증 기능 제공
  • 버그 수정

    • 응원 이미지가 없는 경우 응답의 이미지 URL이 null로 반환되도록 수정
  • 리팩터링

    • 이미지 관련 서비스 및 엔티티에서 문자열 기반 이미지 키를 도메인 객체(ImageKey)로 대체
    • AWS S3 직접 접근 로직을 파일 클라이언트로 추상화하여 의존성 및 예외 처리를 단순화
  • 테스트

    • 파일 업로드, 프리사인 URL, 이미지 도메인 객체, 캐시, 서비스 계층 등 다양한 단위 및 통합 테스트 추가 및 개선
    • 테스트 전반에서 이미지 키 타입 일관성 확보 및 입력값 검증 케이스 확장
  • 스타일

    • 테스트 코드에서 이미지 파일의 MIME 타입 명시로 요청 구성 명확화
  • 문서화

    • 테스트 및 예외 상황에 대한 문서화 테스트 케이스 보강

@coderabbitai
Copy link

coderabbitai bot commented Jul 23, 2025

Walkthrough

이미지 파일 처리 로직이 대대적으로 리팩토링되었습니다. 도메인 객체 ImageImageKey가 도입되어 이미지 업로드와 presigned URL 생성이 도메인 중심으로 변경되었고, AWS S3 연동은 새로운 FileClient 컴포넌트로 추상화되었습니다. 서비스, 저장소, 테스트 전반에 걸쳐 관련 타입 및 메서드 시그니처가 일관되게 수정되었습니다.

Changes

파일/경로 요약 변경 내용 요약
src/main/java/eatda/client/file/FileClient.java, src/test/java/eatda/client/file/FileClientTest.java S3 파일 업로드 및 presigned URL 생성을 담당하는 FileClient 클래스 및 테스트 추가
src/main/java/eatda/domain/Image.java, src/test/java/eatda/domain/ImageTest.java 이미지 도메인 객체 Image 및 관련 검증/확장자 추출 테스트 추가
src/main/java/eatda/domain/ImageKey.java, src/test/java/eatda/domain/ImageKeyTest.java 이미지 키 도메인 객체 ImageKey 및 isEmpty 테스트 추가
src/main/java/eatda/domain/article/Article.java, src/main/java/eatda/domain/store/Cheer.java, src/main/java/eatda/domain/story/Story.java 엔티티의 imageKey 필드를 String에서 ImageKey(임베디드)로 변경, 생성자 및 검증 로직 수정
src/main/java/eatda/repository/store/CheerRepository.java, src/test/java/eatda/repository/store/CheerRepositoryTest.java CheerRepository의 imageKey 반환 타입을 Optional → Optional로 변경 및 테스트 반영
src/main/java/eatda/service/article/ArticleService.java, src/main/java/eatda/service/store/CheerService.java, src/main/java/eatda/service/store/StoreService.java, src/main/java/eatda/service/story/StoryService.java 서비스 계층에서 이미지 처리 관련 파라미터 및 반환 타입을 도메인 객체로 변경, 메서드명 일관화(getPresignedUrl → getPreSignedUrl 등)
src/main/java/eatda/storage/image/ExternalImageStorage.java, src/main/java/eatda/storage/image/ImageStorage.java, src/main/java/eatda/storage/image/CachePreSignedUrlStorage.java, src/test/java/eatda/storage/image/ExternalImageStorageTest.java, src/test/java/eatda/storage/image/ImageStorageTest.java, src/test/java/eatda/storage/image/CachePreSignedUrlStorageTest.java 이미지 저장소 계층 리팩토링: FileClient 도입, String 기반에서 도메인 객체 기반으로 전환, 캐시 키 타입 변경 및 테스트 추가/수정
src/test/java/eatda/controller/BaseControllerTest.java, src/test/java/eatda/controller/store/CheerControllerTest.java, src/test/java/eatda/controller/story/StoryControllerTest.java, src/test/java/eatda/document/store/CheerDocumentTest.java, src/test/java/eatda/document/story/StoryDocumentTest.java 컨트롤러/문서 테스트에서 이미지 파라미터 및 타입 변경, multipart content-type 명시 등 테스트 반영
src/test/java/eatda/domain/store/CheerTest.java, src/test/java/eatda/domain/story/StoryTest.java 도메인 테스트에서 imageKey 타입 변경 및 파라미터화, 중복 제거, 검증 케이스 확장
src/test/java/eatda/fixture/ArticleGenerator.java, src/test/java/eatda/fixture/CheerGenerator.java 테스트 fixture에서 imageKey 타입을 String에서 ImageKey로 변경, 관련 메서드 수정/삭제
src/test/java/eatda/service/BaseServiceTest.java, src/test/java/eatda/service/store/CheerServiceTest.java, src/test/java/eatda/service/story/StoryServiceTest.java 서비스 테스트에서 imageKey 및 storage 관련 mock/검증 로직을 도메인 객체 기반으로 변경

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant ImageService
    participant ImageStorage
    participant ExternalImageStorage
    participant FileClient
    participant AWS_S3

    Client->>ImageService: 이미지 업로드 요청 (Image)
    ImageService->>ImageStorage: upload(Image)
    ImageStorage->>ExternalImageStorage: upload(Image)
    ExternalImageStorage->>FileClient: upload(MultipartFile, key)
    FileClient->>AWS_S3: S3 putObject
    AWS_S3-->>FileClient: 업로드 결과 반환
    FileClient-->>ExternalImageStorage: key 반환
    ExternalImageStorage-->>ImageStorage: ImageKey 반환
    ImageStorage-->>ImageService: ImageKey 반환

    Client->>ImageService: presigned URL 요청 (ImageKey)
    ImageService->>ImageStorage: getPreSignedUrl(ImageKey)
    ImageStorage->>CachePreSignedUrlStorage: get(ImageKey)
    alt 캐시에 없음
        ImageStorage->>ExternalImageStorage: getPreSignedUrl(ImageKey)
        ExternalImageStorage->>FileClient: getPreSignedUrl(key, duration)
        FileClient->>AWS_S3: presignGetObject
        AWS_S3-->>FileClient: presigned URL 반환
        FileClient-->>ExternalImageStorage: URL 반환
        ExternalImageStorage-->>ImageStorage: URL 반환
        ImageStorage->>CachePreSignedUrlStorage: put(ImageKey, URL)
        ImageStorage-->>ImageService: URL 반환
    else 캐시에 있음
        CachePreSignedUrlStorage-->>ImageStorage: URL 반환
        ImageStorage-->>ImageService: URL 반환
    end
Loading

Estimated code review effort

4 (~90분)

Possibly related PRs

Suggested labels

refactor

Poem

🐰
새 옷 입은 이미지, 도메인 품에 안기네
String은 안녕, ImageKey로 인사해
FileClient가 S3로 척척
presigned URL도 캐시에 쏙쏙
토끼는 기뻐 깡총깡총,
리팩토링 끝! 코드가 한결 단단해졌네
🖼️✨

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@sonarqubecloud
Copy link

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (5)
src/main/java/eatda/client/file/FileClient.java (2)

31-47: 파일 업로드 로직에서 리소스 관리를 개선하세요.

현재 구현에서 file.getInputStream()이 예외 발생 시 적절히 닫히지 않을 수 있습니다. AWS SDK의 RequestBody.fromInputStream()은 스트림을 자동으로 관리하지만, 명시적인 리소스 관리가 더 안전합니다.

 public String upload(MultipartFile file, String fileKey) {
     PutObjectRequest request = PutObjectRequest.builder()
             .bucket(bucket)
             .key(fileKey)
             .contentType(file.getContentType())
             .build();

     try {
-        s3Client.putObject(request, RequestBody.fromInputStream(file.getInputStream(), file.getSize()));
+        try (var inputStream = file.getInputStream()) {
+            s3Client.putObject(request, RequestBody.fromInputStream(inputStream, file.getSize()));
+        }
         return fileKey;
     } catch (Exception exception) {
         throw new BusinessException(BusinessErrorCode.FILE_UPLOAD_FAILED);
     }
 }

44-46: TODO 주석의 구체적인 예외 처리 계획을 확인하세요.

AWS SDK의 구체적인 예외들에 대한 처리 계획이 명시되어 있습니다. 이러한 세분화된 예외 처리가 언제 구현될 예정인지 확인이 필요합니다.

구체적인 AWS 예외 처리 구현을 도와드릴까요? 새로운 이슈를 생성하여 이 작업을 추적하시겠습니까?

src/test/java/eatda/storage/image/CachePreSignedUrlStorageTest.java (1)

31-40: ImageKey 래퍼 사용이 적절하지만 키 형식 검증을 고려해보세요.

ImageKey로 래핑하여 캐시 작업을 수행하는 것은 도메인 주도 설계 관점에서 좋습니다. 그러나 하드코딩된 키 형식("story/550e8400-e29b-41d4-a716-446655440000.jpg")이 실제 ImageKey의 유효성 검증 로직과 일치하는지 확인이 필요합니다.

테스트에서 더 명확한 키 형식을 사용하거나, ImageKey 생성을 위한 테스트 헬퍼 메서드를 고려해보세요:

-        String key = "story/550e8400-e29b-41d4-a716-446655440000.jpg";
+        String key = ImageTestFixture.createValidImageKey();
src/main/java/eatda/domain/Image.java (2)

26-30: Content Type 검증 로직을 개선해보세요.

현재 검증 로직은 file이 null이 아닐 때만 content type을 검사합니다. 하지만 더 포괄적인 이미지 MIME 타입 지원을 고려해보세요.

다음과 같이 개선할 수 있습니다:

-    private static final Set<String> ALLOWED_CONTENT_TYPES = Set.of("image/jpg", "image/jpeg", "image/png");
+    private static final Set<String> ALLOWED_CONTENT_TYPES = Set.of("image/jpeg", "image/jpg", "image/png", "image/webp");

참고: "image/jpg"는 표준이 아니므로 "image/jpeg"만 사용하는 것이 좋습니다.


32-41: 파일 확장자 추출 로직이 복잡합니다.

확장자 추출 로직이 다양한 엣지 케이스를 처리하고 있지만, 복잡성이 높아 테스트와 유지보수가 어려울 수 있습니다.

더 명확한 로직으로 리팩토링을 고려해보세요:

public String getExtension() {
+    if (file == null) {
+        return DEFAULT_CONTENT_TYPE;
+    }
    String filename = file.getOriginalFilename();
-    if (filename == null
-            || filename.lastIndexOf(EXTENSION_DELIMITER) == -1
-            || filename.startsWith(EXTENSION_DELIMITER)
-            || filename.endsWith(EXTENSION_DELIMITER)) {
+    if (filename == null || !isValidFilename(filename)) {
        return DEFAULT_CONTENT_TYPE;
    }
    return filename.substring(filename.lastIndexOf(EXTENSION_DELIMITER) + 1);
}

+private boolean isValidFilename(String filename) {
+    int lastDotIndex = filename.lastIndexOf(EXTENSION_DELIMITER);
+    return lastDotIndex > 0 && lastDotIndex < filename.length() - 1;
+}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fcd83b2 and 00e4f3d.

📒 Files selected for processing (33)
  • src/main/java/eatda/client/file/FileClient.java (1 hunks)
  • src/main/java/eatda/domain/Image.java (1 hunks)
  • src/main/java/eatda/domain/ImageKey.java (1 hunks)
  • src/main/java/eatda/domain/article/Article.java (2 hunks)
  • src/main/java/eatda/domain/store/Cheer.java (3 hunks)
  • src/main/java/eatda/domain/story/Story.java (7 hunks)
  • src/main/java/eatda/repository/store/CheerRepository.java (2 hunks)
  • src/main/java/eatda/service/article/ArticleService.java (1 hunks)
  • src/main/java/eatda/service/store/CheerService.java (3 hunks)
  • src/main/java/eatda/service/store/StoreService.java (1 hunks)
  • src/main/java/eatda/service/story/StoryService.java (4 hunks)
  • src/main/java/eatda/storage/image/CachePreSignedUrlStorage.java (2 hunks)
  • src/main/java/eatda/storage/image/ExternalImageStorage.java (1 hunks)
  • src/main/java/eatda/storage/image/ImageStorage.java (3 hunks)
  • src/test/java/eatda/client/file/FileClientTest.java (1 hunks)
  • src/test/java/eatda/controller/BaseControllerTest.java (3 hunks)
  • src/test/java/eatda/controller/store/CheerControllerTest.java (1 hunks)
  • src/test/java/eatda/controller/story/StoryControllerTest.java (1 hunks)
  • src/test/java/eatda/document/store/CheerDocumentTest.java (2 hunks)
  • src/test/java/eatda/document/story/StoryDocumentTest.java (1 hunks)
  • src/test/java/eatda/domain/ImageKeyTest.java (1 hunks)
  • src/test/java/eatda/domain/ImageTest.java (1 hunks)
  • src/test/java/eatda/domain/store/CheerTest.java (2 hunks)
  • src/test/java/eatda/domain/story/StoryTest.java (4 hunks)
  • src/test/java/eatda/fixture/ArticleGenerator.java (2 hunks)
  • src/test/java/eatda/fixture/CheerGenerator.java (4 hunks)
  • src/test/java/eatda/repository/store/CheerRepositoryTest.java (3 hunks)
  • src/test/java/eatda/service/BaseServiceTest.java (3 hunks)
  • src/test/java/eatda/service/store/CheerServiceTest.java (1 hunks)
  • src/test/java/eatda/service/story/StoryServiceTest.java (6 hunks)
  • src/test/java/eatda/storage/image/CachePreSignedUrlStorageTest.java (1 hunks)
  • src/test/java/eatda/storage/image/ExternalImageStorageTest.java (1 hunks)
  • src/test/java/eatda/storage/image/ImageStorageTest.java (2 hunks)
🧠 Learnings (5)
src/test/java/eatda/service/store/CheerServiceTest.java (1)

Learnt from: leegwichan
PR: #90
File: src/main/java/eatda/service/store/CheerService.java:35-46
Timestamp: 2025-07-20T05:38:13.430Z
Learning: CheerService에서 각 컴포넌트(storeSearchFilter, memberRepository, imageService)들이 자신의 책임 범위 내에서 검증을 수행하므로 서비스 레이어에서 추가적인 중복 검증이 불필요함. 각 레이어의 책임 분리가 잘 되어 있는 구조.

src/main/java/eatda/service/store/CheerService.java (1)

Learnt from: leegwichan
PR: #90
File: src/main/java/eatda/service/store/CheerService.java:35-46
Timestamp: 2025-07-20T05:38:13.430Z
Learning: CheerService에서 각 컴포넌트(storeSearchFilter, memberRepository, imageService)들이 자신의 책임 범위 내에서 검증을 수행하므로 서비스 레이어에서 추가적인 중복 검증이 불필요함. 각 레이어의 책임 분리가 잘 되어 있는 구조.

src/test/java/eatda/fixture/CheerGenerator.java (1)

Learnt from: lvalentine6
PR: #76
File: src/main/java/eatda/domain/store/Cheer.java:50-61
Timestamp: 2025-07-15T09:42:54.091Z
Learning: Cheer 엔티티의 생성자는 데이터베이스 시딩을 위해 의도적으로 ID 매개변수를 받도록 설계되었습니다. 시드 데이터에서 명시적으로 ID를 설정하기 때문에 이 패턴이 필요합니다.

src/test/java/eatda/controller/BaseControllerTest.java (1)

Learnt from: leegwichan
PR: #60
File: src/main/java/eatda/client/map/MapClient.java:24-41
Timestamp: 2025-07-09T07:54:18.446Z
Learning: In MapClient.java, the developer prefers to keep Kakao API-specific values (baseUrl, pageSize) hardcoded rather than extracting them to configuration properties, as these values are inherently tied to the Kakao API specification.

src/test/java/eatda/domain/ImageTest.java (1)

Learnt from: lvalentine6
PR: #68
File: src/main/java/eatda/service/common/ImageService.java:24-24
Timestamp: 2025-07-09T20:11:28.800Z
Learning: ImageService에서 MIME 타입 검증 시 "image/jpg"와 "image/jpeg" 모두 허용해야 함. "image/jpg"는 비표준이지만 실제 환경에서 여전히 사용되어 호환성 문제 방지를 위해 두 타입 모두 지원하는 것이 실용적임.

🧬 Code Graph Analysis (6)
src/test/java/eatda/controller/store/CheerControllerTest.java (1)
src/test/java/eatda/util/ImageUtils.java (1)
  • ImageUtils (8-22)
src/test/java/eatda/controller/story/StoryControllerTest.java (1)
src/test/java/eatda/util/ImageUtils.java (1)
  • ImageUtils (8-22)
src/test/java/eatda/document/story/StoryDocumentTest.java (1)
src/test/java/eatda/util/ImageUtils.java (1)
  • ImageUtils (8-22)
src/test/java/eatda/document/store/CheerDocumentTest.java (1)
src/test/java/eatda/util/ImageUtils.java (1)
  • ImageUtils (8-22)
src/main/java/eatda/storage/image/ExternalImageStorage.java (2)
src/main/java/eatda/storage/image/ImageStorage.java (1)
  • Component (10-44)
src/main/java/eatda/storage/image/CachePreSignedUrlStorage.java (1)
  • Component (10-28)
src/main/java/eatda/domain/ImageKey.java (1)
src/main/java/eatda/domain/Image.java (1)
  • Getter (10-50)
🧰 Additional context used
🧠 Learnings (5)
src/test/java/eatda/service/store/CheerServiceTest.java (1)

Learnt from: leegwichan
PR: #90
File: src/main/java/eatda/service/store/CheerService.java:35-46
Timestamp: 2025-07-20T05:38:13.430Z
Learning: CheerService에서 각 컴포넌트(storeSearchFilter, memberRepository, imageService)들이 자신의 책임 범위 내에서 검증을 수행하므로 서비스 레이어에서 추가적인 중복 검증이 불필요함. 각 레이어의 책임 분리가 잘 되어 있는 구조.

src/main/java/eatda/service/store/CheerService.java (1)

Learnt from: leegwichan
PR: #90
File: src/main/java/eatda/service/store/CheerService.java:35-46
Timestamp: 2025-07-20T05:38:13.430Z
Learning: CheerService에서 각 컴포넌트(storeSearchFilter, memberRepository, imageService)들이 자신의 책임 범위 내에서 검증을 수행하므로 서비스 레이어에서 추가적인 중복 검증이 불필요함. 각 레이어의 책임 분리가 잘 되어 있는 구조.

src/test/java/eatda/fixture/CheerGenerator.java (1)

Learnt from: lvalentine6
PR: #76
File: src/main/java/eatda/domain/store/Cheer.java:50-61
Timestamp: 2025-07-15T09:42:54.091Z
Learning: Cheer 엔티티의 생성자는 데이터베이스 시딩을 위해 의도적으로 ID 매개변수를 받도록 설계되었습니다. 시드 데이터에서 명시적으로 ID를 설정하기 때문에 이 패턴이 필요합니다.

src/test/java/eatda/controller/BaseControllerTest.java (1)

Learnt from: leegwichan
PR: #60
File: src/main/java/eatda/client/map/MapClient.java:24-41
Timestamp: 2025-07-09T07:54:18.446Z
Learning: In MapClient.java, the developer prefers to keep Kakao API-specific values (baseUrl, pageSize) hardcoded rather than extracting them to configuration properties, as these values are inherently tied to the Kakao API specification.

src/test/java/eatda/domain/ImageTest.java (1)

Learnt from: lvalentine6
PR: #68
File: src/main/java/eatda/service/common/ImageService.java:24-24
Timestamp: 2025-07-09T20:11:28.800Z
Learning: ImageService에서 MIME 타입 검증 시 "image/jpg"와 "image/jpeg" 모두 허용해야 함. "image/jpg"는 비표준이지만 실제 환경에서 여전히 사용되어 호환성 문제 방지를 위해 두 타입 모두 지원하는 것이 실용적임.

🧬 Code Graph Analysis (6)
src/test/java/eatda/controller/store/CheerControllerTest.java (1)
src/test/java/eatda/util/ImageUtils.java (1)
  • ImageUtils (8-22)
src/test/java/eatda/controller/story/StoryControllerTest.java (1)
src/test/java/eatda/util/ImageUtils.java (1)
  • ImageUtils (8-22)
src/test/java/eatda/document/story/StoryDocumentTest.java (1)
src/test/java/eatda/util/ImageUtils.java (1)
  • ImageUtils (8-22)
src/test/java/eatda/document/store/CheerDocumentTest.java (1)
src/test/java/eatda/util/ImageUtils.java (1)
  • ImageUtils (8-22)
src/main/java/eatda/storage/image/ExternalImageStorage.java (2)
src/main/java/eatda/storage/image/ImageStorage.java (1)
  • Component (10-44)
src/main/java/eatda/storage/image/CachePreSignedUrlStorage.java (1)
  • Component (10-28)
src/main/java/eatda/domain/ImageKey.java (1)
src/main/java/eatda/domain/Image.java (1)
  • Getter (10-50)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (60)
src/main/java/eatda/client/file/FileClient.java (2)

23-29: 생성자 구현이 깔끔합니다.

의존성 주입이 적절하게 구현되어 있고, @Value 어노테이션을 통한 설정값 주입도 올바릅니다.


49-64: PreSigned URL 생성 로직이 적절합니다.

메서드 구현이 깔끔하고 예외 처리도 일관성 있게 되어 있습니다.

src/main/java/eatda/service/article/ArticleService.java (1)

27-27: 메서드 명명 변경 일관성 확인 및 승인

  • 코드베이스 전체에서 getPresignedUrl 호출이 완전히 제거됨을 확인했습니다.
  • 모든 위치에서 getPreSignedUrl로 일관되게 적용되었습니다.

승인합니다.

src/test/java/eatda/document/story/StoryDocumentTest.java (1)

71-71: 테스트 개선: 명시적 MIME 타입 지정이 좋습니다.

multiPart 호출에 명시적으로 "image/png" MIME 타입을 지정한 것이 테스트의 정확성을 향상시킵니다. 실제 HTTP 요청과 더 유사한 환경을 만들어줍니다.

src/test/java/eatda/service/store/CheerServiceTest.java (1)

124-124: 테스트 로직 수정이 정확합니다.

이미지가 제공되지 않은 경우(null 파라미터) imageUrl()null이어야 한다는 것이 논리적으로 맞습니다. 이전의 isNotNull() 검증은 잘못된 것이었습니다.

src/test/java/eatda/controller/store/CheerControllerTest.java (1)

30-30: 테스트 개선: 명시적 MIME 타입 지정이 좋습니다.

다른 테스트 파일들과 일관되게 multiPart 호출에 명시적으로 "image/png" MIME 타입을 지정한 것이 테스트의 정확성을 향상시킵니다.

src/main/java/eatda/service/store/StoreService.java (1)

41-41: 메서드 명 변경이 올바르게 적용되었습니다.

getPresignedUrl에서 getPreSignedUrl로의 메서드 명 변경이 일관성 있게 적용되어 camelCase 명명 규칙을 따르고 있습니다.

src/test/java/eatda/controller/story/StoryControllerTest.java (1)

45-45: 멀티파트 이미지 업로드에 MIME 타입이 명시적으로 지정되었습니다.

"image/png" 컨텐츠 타입을 명시적으로 지정하여 테스트의 정확성이 향상되었습니다. 실제 환경에서의 동작을 더 정확하게 모사합니다.

src/test/java/eatda/document/store/CheerDocumentTest.java (1)

79-79: 문서화 테스트에서 이미지 MIME 타입이 명시적으로 지정되었습니다.

두 테스트 메서드 모두에서 "image/png" 컨텐츠 타입을 명시적으로 지정하여 API 문서화의 정확성이 향상되었습니다. API 사용자에게 더 명확한 예시를 제공합니다.

Also applies to: 108-108

src/test/java/eatda/domain/store/CheerTest.java (1)

7-7: 도메인 객체 ImageKey 사용으로 타입 안전성이 향상되었습니다.

원시 문자열 대신 ImageKey 객체를 사용하여 도메인 모델의 캡슐화와 타입 안전성이 개선되었습니다. 테스트는 새로운 도메인 모델을 올바르게 반영합니다.

Also applies to: 37-40

src/test/java/eatda/controller/BaseControllerTest.java (1)

14-14: 테스트 인프라가 새로운 도메인 모델에 맞게 업데이트되었습니다.

ImageKey 도메인 객체 사용, 메서드 명 변경(getPreSignedUrl), 그리고 업데이트된 메서드 시그니처에 맞는 Mock 설정이 올바르게 적용되었습니다. 모든 컨트롤러 테스트가 새로운 도메인 모델과 일관되게 작동할 수 있습니다.

Also applies to: 50-50, 117-118

src/test/java/eatda/repository/store/CheerRepositoryTest.java (1)

5-5: 리팩토링이 일관되게 적용되었습니다.

ImageKey 도메인 객체 도입에 따라 테스트 코드가 올바르게 업데이트되었습니다. 타입 안전성이 개선되고 도메인 모델과 일치합니다.

Also applies to: 27-27, 29-29, 40-40

src/main/java/eatda/repository/store/CheerRepository.java (1)

3-3: 레포지토리 인터페이스가 올바르게 업데이트되었습니다.

ImageKey 도메인 객체 사용으로 타입 안전성이 향상되었으며, JPQL 쿼리는 JPA가 임베디드 타입을 자동으로 처리하므로 수정이 불필요합니다.

Also applies to: 24-24

src/test/java/eatda/fixture/CheerGenerator.java (2)

3-3: 리팩토링이 적절히 적용되었습니다.

ImageKey 도메인 객체 사용으로 테스트 픽스처가 일관되게 업데이트되었습니다. 상수명 변경도 더 명확한 의미를 전달합니다.

Also applies to: 13-13, 23-23


31-33: ImageKey 생성자 null 안전성 확인 완료
new ImageKey(null) 시 예외가 발생하지 않으며, isEmpty() 메서드가 value == null을 커버하므로 NPE 우려가 없습니다.
현재 구현대로 유지하셔도 무방합니다.

src/test/java/eatda/fixture/ArticleGenerator.java (1)

3-3: 리팩토링이 일관되게 적용되었습니다.

ImageKey 도메인 객체 도입에 맞춰 테스트 픽스처가 올바르게 업데이트되었습니다. 타입 안전성이 개선되었습니다.

Also applies to: 39-39

src/main/java/eatda/storage/image/CachePreSignedUrlStorage.java (1)

3-3: 캐시 레이어 리팩토링이 올바르게 구현되었습니다.

ImageKey 도메인 객체를 사용하여 타입 안전성을 확보하면서도, 내부적으로는 getValue()를 통해 실제 문자열 값을 사용하여 캐시와의 호환성을 유지했습니다. 깔끔한 구현입니다.

Also applies to: 21-22, 25-26

src/test/java/eatda/domain/ImageKeyTest.java (1)

10-38: 테스트 커버리지가 포괄적이고 잘 구성되어 있습니다.

ImageKeyisEmpty() 메서드에 대한 테스트가 다양한 케이스를 잘 다루고 있습니다:

  • null, 빈 문자열, 공백 문자열 등 빈 값 케이스
  • UUID 기반의 유효한 이미지 키 케이스
  • 매개변수화된 테스트를 통한 효율적인 테스트 구조
src/main/java/eatda/domain/ImageKey.java (1)

10-42: 잘 설계된 값 객체입니다.

ImageKey 클래스가 JPA 임베디드 값 객체로서 올바르게 구현되었습니다:

  • 적절한 JPA 어노테이션 사용 (@Embeddable, @Column)
  • isBlank() 메서드로 null과 공백 처리
  • Objects 유틸리티를 사용한 올바른 equals/hashCode 구현
  • 값 객체 패턴에 맞는 불변성 보장
src/test/java/eatda/service/BaseServiceTest.java (1)

9-9: 도메인 모델 리팩토링에 맞춰 테스트 기반 클래스가 일관되게 업데이트되었습니다.

  • MOCKED_IMAGE_KEYString에서 ImageKey 인스턴스로 변경
  • Mock 설정이 새로운 메서드 시그니처에 맞춰 조정 (getPreSignedUrl, upload 메서드)
  • 매개변수 매처도 도메인 타입에 맞게 수정

Also applies to: 29-29, 67-68

src/main/java/eatda/domain/article/Article.java (1)

4-4: 엔티티가 도메인 값 객체 사용에 맞춰 일관되게 리팩토링되었습니다.

Article 엔티티의 imageKey 필드가 원시 String에서 ImageKey 임베디드 값 객체로 변경되어:

  • 타입 안전성 향상
  • JPA @Embedded 어노테이션으로 적절한 매핑
  • @NotNull 제약조건으로 데이터 무결성 보장
  • 다른 엔티티들(Story, Cheer)과 일관된 패턴 적용

Also applies to: 6-6, 12-12, 36-38, 40-40

src/main/java/eatda/service/store/CheerService.java (1)

9-11: 서비스 레이어가 도메인 중심 설계에 맞춰 잘 리팩토링되었습니다.

개선된 사항들:

  • 매개변수 이름 명확화 (imageimageFile)
  • Image 도메인 객체로 파일과 도메인 컨텍스트 캡슐화
  • ImageKey 반환 타입으로 타입 안전성 향상
  • 메서드명 일관성 개선 (getPreSignedUrl)

기존 비즈니스 로직을 유지하면서 도메인 모델과 타입 안전성이 크게 개선되었습니다.

Also applies to: 42-42, 48-48, 53-53, 74-74

src/test/java/eatda/storage/image/CachePreSignedUrlStorageTest.java (2)

15-25: 테스트 설정이 적절합니다.

CachePreSignedUrlStorage의 테스트를 위한 설정이 올바르게 구성되어 있습니다. SimpleCacheManager와 ConcurrentMapCache를 사용한 테스트 환경 구성이 적절합니다.


42-49: 존재하지 않는 키에 대한 테스트가 적절합니다.

빈 Optional 반환을 확인하는 네거티브 테스트 케이스가 잘 구현되어 있습니다. 캐시 미스 상황을 적절히 검증합니다.

src/main/java/eatda/domain/Image.java (2)

10-24: 도메인 객체 설계가 우수합니다.

Image 도메인 객체가 잘 설계되었습니다. ImageDomain과 MultipartFile을 캡슐화하고 적절한 검증 로직을 포함하고 있습니다.


47-49: isEmpty 메서드가 적절합니다.

파일의 존재 여부와 비어있는지를 적절히 검사하는 로직입니다.

src/main/java/eatda/service/story/StoryService.java (4)

10-12: 새로운 도메인 객체 임포트가 적절합니다.

Image와 ImageKey 도메인 객체를 임포트하여 도메인 주도 설계를 잘 적용했습니다.


41-45: Image 도메인 객체 사용이 우수합니다.

ImageDomain.STORY와 함께 Image 객체를 생성하여 도메인 컨텍스트를 명확히 하고, ImageKey를 반환받는 구조가 잘 설계되었습니다. 매개변수명도 'imageFile'로 더 명확해졌습니다.


86-86: 메서드명 변경이 일관성을 높입니다.

getPreSignedUrl로 메서드명이 변경되어 네이밍 일관성이 개선되었습니다.


104-104: ImageKey 사용으로 타입 안전성이 향상되었습니다.

ImageKey 객체를 사용하여 원시 문자열 대신 타입 안전한 방식으로 이미지 키를 처리하고 있습니다.

src/test/java/eatda/service/story/StoryServiceTest.java (5)

13-13: ImageKey 임포트로 도메인 객체 일관성 확보

ImageDomain 대신 ImageKey를 임포트하여 리팩토링된 도메인 모델과 일치합니다.


40-42: MockMultipartFile 사용이 개선되었습니다.

명시적인 MIME 타입("image/jpeg")을 지정한 MockMultipartFile 사용으로 더 현실적인 테스트 환경을 구성했습니다.


83-84: ImageKey 래퍼 사용이 적절합니다.

원시 문자열 대신 ImageKey 객체를 사용하여 타입 안전성을 확보했습니다.


93-94: 테스트 데이터 생성에서 ImageKey 일관성 유지

Story 엔티티 생성 시 ImageKey 객체를 사용하여 도메인 모델 변경사항과 일치합니다.


126-127: 모킹 업데이트가 정확합니다.

getPreSignedUrl 메서드명 변경과 ImageKey 매개변수 사용이 실제 구현과 일치하도록 모킹이 적절히 업데이트되었습니다.

src/main/java/eatda/domain/store/Cheer.java (4)

4-4: ImageKey 도메인 객체 임포트가 적절합니다.

ImageKey 임포트로 도메인 주도 설계를 위한 값 객체 사용을 준비했습니다.


43-44: JPA @Embedded 사용이 우수합니다.

@column 대신 @Embedded를 사용하여 ImageKey 값 객체를 적절히 임베드했습니다. 이는 도메인 모델의 응집성을 높이는 좋은 설계입니다.


49-57: 생성자 업데이트가 일관성 있습니다.

ImageKey 매개변수를 받도록 생성자가 적절히 업데이트되었습니다. ImageKey 자체에서 검증을 담당하므로 별도의 imageKey 검증 로직 제거가 적절합니다.


59-62: 관리자 권한 생성자도 일관성 있게 업데이트

ImageKey를 사용하는 관리자 권한 생성자도 적절히 업데이트되어 전체적인 일관성을 유지합니다.

src/main/java/eatda/domain/story/Story.java (1)

60-62: ImageKey 도메인 객체 도입이 적절합니다.

값 객체(Value Object)로 ImageKey를 도입하고 @Embedded를 사용한 것은 도메인 중심 설계 원칙에 잘 부합합니다. 검증 로직도 ImageKey의 isEmpty() 메서드를 활용하여 책임이 적절히 분리되었습니다.

Also applies to: 150-154

src/test/java/eatda/domain/ImageTest.java (1)

17-79: Image 도메인 객체에 대한 테스트가 포괄적입니다.

이미지 타입 검증과 확장자 추출 로직에 대한 테스트가 잘 작성되었습니다. 특히 "image/jpg"와 "image/jpeg" 모두를 지원하는 것과 엣지 케이스(확장자가 없거나 잘못된 형식)에 대한 처리가 적절합니다.

src/test/java/eatda/storage/image/ExternalImageStorageTest.java (1)

21-43: FileClient 추상화를 통한 테스트 개선이 우수합니다.

AWS SDK에 대한 직접적인 의존성을 제거하고 FileClient 추상화를 사용하여 테스트가 더 간결하고 유지보수하기 쉬워졌습니다. ImageKey 형식 검증을 위한 정규식 패턴 매칭도 적절합니다.

src/main/java/eatda/storage/image/ImageStorage.java (1)

17-43: 도메인 객체를 활용한 리팩토링이 잘 구현되었습니다.

Image와 ImageKey 도메인 객체를 사용하여 타입 안전성이 향상되었고, null/empty 케이스 처리가 적절합니다. 캐싱 로직도 그대로 유지되어 성능 측면에서도 문제가 없습니다.

src/test/java/eatda/storage/image/ImageStorageTest.java (1)

33-112: 도메인 객체를 활용한 테스트 업데이트가 완벽합니다.

Image와 ImageKey 도메인 객체를 사용하도록 테스트가 잘 업데이트되었습니다. 캐시 동작, null/empty 처리, 그리고 외부 저장소와의 상호작용이 모두 적절히 검증되고 있습니다.

src/test/java/eatda/client/file/FileClientTest.java (6)

29-42: 테스트 설정이 잘 구성되어 있습니다.

FileClient의 의존성들을 적절히 모킹하고 있으며, 테스트 격리가 잘 되어 있습니다.


47-57: 파일 업로드 성공 케이스 테스트가 적절합니다.

MockMultipartFile을 사용하여 파일 업로드를 테스트하고 있으며, 반환값 검증도 올바르게 수행하고 있습니다.


59-69: 예외 처리 테스트가 잘 구현되어 있습니다.

SdkClientException 발생 시 BusinessException으로 적절히 변환되는지 검증하고 있으며, 에러 코드도 정확히 확인하고 있습니다.


75-84: PreSigned URL 생성 테스트가 올바르게 구현되어 있습니다.

모킹을 통해 URL 생성을 테스트하고 있으며, Duration 파라미터도 적절히 전달하고 있습니다.


86-90: 헬퍼 메서드가 테스트 가독성을 향상시킵니다.

PresignedGetObjectRequest 모킹을 위한 헬퍼 메서드가 테스트 코드의 중복을 줄이고 가독성을 개선합니다.


92-102: PreSigned URL 생성 실패 시나리오도 적절히 테스트되고 있습니다.

예외 발생 시 올바른 BusinessException과 에러 코드가 반환되는지 검증하고 있습니다.

src/main/java/eatda/storage/image/ExternalImageStorage.java (4)

17-21: 생성자 리팩토링이 의존성 관리를 개선했습니다.

FileClient를 주입받아 AWS SDK 의존성을 추상화했으며, 단일 책임 원칙을 잘 따르고 있습니다.


23-27: 업로드 메서드가 도메인 객체를 잘 활용하고 있습니다.

Image 도메인 객체에서 도메인명과 확장자를 추출하여 키를 생성하고, FileClient에 위임하는 구조가 깔끔합니다. ImageKey 반환도 적절합니다.


29-32: 키 생성 로직이 명확하고 일관성 있습니다.

UUID를 사용한 고유 키 생성과 도메인명/확장자를 조합한 경로 구조가 잘 설계되어 있습니다.


34-36: PreSigned URL 생성이 도메인 객체를 적절히 활용합니다.

ImageKey 도메인 객체를 받아 값을 추출하고 고정된 Duration으로 FileClient에 위임하는 구조가 일관성 있습니다.

src/test/java/eatda/domain/story/StoryTest.java (6)

22-35: 공통 빌더 설정이 테스트 코드 중복을 효과적으로 제거했습니다.

@beforeeach에서 defaultStoryBuilder를 설정하여 모든 테스트에서 재사용할 수 있도록 했으며, 새로운 ImageKey 도메인 객체도 적절히 사용하고 있습니다.


65-74: 매개변수화된 테스트로 검증 로직이 개선되었습니다.

@ParameterizedTest와 @NullAndEmptySource, @valuesource를 사용하여 null, 빈 문자열, 공백 문자열에 대한 검증을 효율적으로 테스트하고 있습니다.


76-85: 가게 이름 검증도 매개변수화된 테스트로 잘 구현되었습니다.

동일한 패턴으로 가게 이름 검증 테스트를 구현하여 일관성을 유지하고 있습니다.


96-105: 지번 주소 검증 테스트도 일관된 패턴을 따르고 있습니다.

매개변수화된 테스트 패턴을 일관되게 적용하여 코드 중복을 줄이고 가독성을 향상시켰습니다.


120-129: 설명 검증 테스트도 매개변수화된 테스트로 개선되었습니다.

동일한 검증 패턴을 설명 필드에도 적용하여 테스트 일관성을 유지하고 있습니다.


132-138: 이미지 키 검증이 도메인 객체를 적절히 활용합니다.

ImageKey를 null로 설정하여 빈 이미지 검증을 테스트하고 있으며, 새로운 도메인 모델과 일치합니다.

Copy link
Member

@lvalentine6 lvalentine6 left a comment

Choose a reason for hiding this comment

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

이번 PR도 고생하셨습니다! 🎉
회의에서 논의된 내용들이 잘 반영되어 있는것 같아요
LGTM입니다!

@leegwichan leegwichan merged commit 48ad47d into develop Jul 24, 2025
10 checks passed
@leegwichan leegwichan deleted the refactor/PRODUCT-195 branch July 24, 2025 02:35
@github-actions
Copy link

🎉 This PR is included in version 1.4.0-develop.29 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@github-actions
Copy link

🎉 This PR is included in version 1.5.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[PRODUCT-195] [Refactor] 이미지 도메인 객체 도입

3 participants