Skip to content

Conversation

@Jjiggu
Copy link
Collaborator

@Jjiggu Jjiggu commented Jun 4, 2025

작업 요약

  • Store CRUD 구현
  • 이미지 업로드 구현
  • 이미지 업로드 동시 요청 시 스레드 고갈로 인한 타임아웃 방지를 위한 Bulkhead 패턴 적용

Issue Link

#15 #14 #8

문제점 및 어려움

  • Bulkhead 패턴 수치 기준값 설정 필요
  • 클린코드 및 테스트 코드 추가 필요

해결 방안

Reference

Summary by CodeRabbit

  • 신규 기능

    • 매장 이미지 업로드 및 삭제 API가 추가되었습니다. 여러 이미지를 한 번에 업로드하거나, 특정 이미지를 삭제할 수 있습니다.
    • 매장 이미지 정보를 여러 개 지원하도록 매장 조회 응답이 개선되었습니다.
    • AWS S3 연동을 통한 이미지 파일 저장 및 삭제 기능이 도입되었습니다.
    • 비동기 이미지 업로드 및 내결함성 지원을 위한 기능이 추가되었습니다.
  • 기능 개선

    • 매장 생성, 수정, 조회 시 단일 이미지 URL 대신 여러 이미지 정보를 반환하도록 변경되었습니다.
    • 매장 관련 API에서 이미지 필드 구조가 변경되었습니다.
    • 매장 정보 수정 시 이름, 위치, 설명을 한 번에 업데이트하는 방식으로 개선되었습니다.
    • 매장 활성화 및 삭제 상태 변경이 명확한 메서드 호출로 대체되었습니다.
  • 버그 수정

    • 매장 관련 요청 및 응답 객체에서 불필요한 이미지 URL 필드가 제거되었습니다.
  • 보안

    • /stores/** 경로에 대한 접근이 인증 없이 가능하도록 허용 범위가 확장되었습니다.

@Jjiggu Jjiggu self-assigned this Jun 4, 2025
@Jjiggu Jjiggu added the enhancement New feature or request label Jun 4, 2025
@coderabbitai
Copy link

coderabbitai bot commented Jun 4, 2025

Walkthrough

이번 변경 사항에서는 AWS S3 연동, 비동기 실행, 탄력성 패턴 도입을 위한 의존성 추가와 함께, 매장 이미지 업로드 및 삭제 기능이 새롭게 구현되었습니다. 매장 관련 DTO와 엔티티 구조가 단일 이미지에서 다중 이미지로 확장되었으며, 이에 맞춰 서비스와 컨트롤러 계층도 전반적으로 개선되었습니다.

Changes

파일/경로 그룹 변경 요약
build.gradle AWS S3, Resilience4j, Spring AOP 관련 의존성 추가
.../config/AsyncConfig.java, .../config/AwsS3Config.java S3 클라이언트 및 비동기 실행기 설정을 위한 Spring 설정 클래스 추가
.../config/SecurityConfig.java /stores/** 경로 인증 예외로 추가
.../global/s3/S3Service.java S3 파일 업로드(비동기) 및 삭제 서비스 클래스 신설
.../store/dto/StoreCreateRequest.java, .../store/dto/StoreCreateResponse.java, .../store/dto/StoreUpdateRequest.java 매장 이미지 URL 필드 제거
.../store/dto/StoreReadDto.java 단일 이미지 URL → 이미지 응답 리스트로 변경, 생성 메서드 시그니처 수정
.../store/dto/StoreReadResponse.java 정적 팩토리 메서드명 fromEntityof로 변경
.../store/model/Store.java 이미지 URL 필드 및 관련 메서드 제거, 상태 변경 메서드 명확화, 정보 일괄 업데이트 메서드 추가
.../store/service/StoreServiceImpl.java 매장 이미지 연동 및 반환, 엔티티 상태 변경 방식 개선
.../storeImage/controller/StoreImageController.java 매장 이미지 업로드 및 삭제 REST 컨트롤러 신설
.../storeImage/dto/StoreImageUploadResponse.java 이미지 업로드 응답 DTO 신설
.../storeImage/model/StoreImage.java 매장 이미지 JPA 엔티티 신설
.../storeImage/repository/StoreImageRepository.java 매장별 이미지 조회용 JPA 리포지토리 신설
.../storeImage/service/StoreImageService.java 이미지 일괄 업로드 및 삭제 서비스 신설

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant StoreImageController
    participant StoreImageService
    participant StoreRepository
    participant S3Service
    participant StoreImageRepository

    Client->>StoreImageController: POST /stores/{storeId}/images (files, types)
    StoreImageController->>StoreImageService: saveAll(storeId, files, types)
    StoreImageService->>StoreRepository: findById(storeId)
    StoreRepository-->>StoreImageService: Store
    loop 각 파일
        StoreImageService->>S3Service: upload(storeId, file)
        S3Service-->>StoreImageService: S3UploadResult(key, url)
        StoreImageService->>StoreImageRepository: save(StoreImage)
    end
    StoreImageService-->>StoreImageController: List<StoreImageUploadResponse>
    StoreImageController-->>Client: 201 Created + 응답

    Client->>StoreImageController: DELETE /stores/image/{imageId}
    StoreImageController->>StoreImageService: delete(imageId)
    StoreImageService->>StoreImageRepository: findById(imageId)
    StoreImageRepository-->>StoreImageService: StoreImage
    StoreImageService->>S3Service: delete(fileKey)
    StoreImageService->>StoreImageRepository: delete(StoreImage)
    StoreImageService-->>StoreImageController: void
    StoreImageController-->>Client: 204 No Content
Loading

Poem

🌱
새로운 이미지가 S3로 날아가네,
매장마다 사진첩이 풍성해졌죠.
삭제도 업로드도 한결 편리하게,
비동기와 탄력성도 든든히 곁들여,
오늘의 코드엔 구름 위 햇살이 비치네요!
☁️🖼️✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b38796b and c7b4e21.

📒 Files selected for processing (3)
  • build.gradle (1 hunks)
  • src/main/java/com/example/gtable/storeImage/controller/StoreImageController.java (1 hunks)
  • src/main/java/com/example/gtable/storeImage/service/StoreImageService.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • build.gradle
  • src/main/java/com/example/gtable/storeImage/controller/StoreImageController.java
  • src/main/java/com/example/gtable/storeImage/service/StoreImageService.java
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 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.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

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.

@Jjiggu Jjiggu linked an issue Jun 4, 2025 that may be closed by this pull request
2 tasks
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: 7

🧹 Nitpick comments (4)
src/main/java/com/example/gtable/storeImage/repository/StoreImageRepository.java (1)

11-15: 표준적인 Spring Data JPA 패턴을 잘 따르고 있습니다

레포지토리 구현이 매우 깔끔하고 Spring Data JPA의 컨벤션을 잘 따르고 있어요. findByStore 메서드명도 직관적이고 명확합니다.

한 가지 추가로 고려해볼 점은 성능 최적화인데요, 스토어 이미지가 많은 경우를 대비해서 다음과 같은 개선을 해보시면 어떨까요?

// 페이징 지원을 위한 메서드 추가 고려
Page<StoreImage> findByStore(Store store, Pageable pageable);

// 또는 이미지 개수가 많지 않다면 정렬 추가
List<StoreImage> findByStoreOrderByCreatedAtDesc(Store store);

현재 구현도 충분히 좋지만, 향후 확장성을 위해 참고해보시면 좋을 것 같습니다.

src/main/java/com/example/gtable/global/config/AwsS3Config.java (1)

24-31: 타입 캐스팅을 더 명시적인 방법으로 개선할 수 있습니다

현재 AmazonS3ClientBuilder.standard().build()의 결과를 AmazonS3Client로 캐스팅하고 있는데, 이는 잠재적인 ClassCastException을 유발할 수 있어요.

다음과 같이 개선해보시면 어떨까요:

-	@Bean
-	public AmazonS3Client amazonS3Client() {
-		BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
-		return (AmazonS3Client)AmazonS3ClientBuilder.standard()
-			.withRegion(region)
-			.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
-			.build();
-	}
+	@Bean
+	public AmazonS3 amazonS3Client() {
+		BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
+		return AmazonS3ClientBuilder.standard()
+			.withRegion(region)
+			.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
+			.build();
+	}

이렇게 하면 인터페이스 타입(AmazonS3)을 반환하여 더 안전하고 유연한 코드가 됩니다.

src/main/java/com/example/gtable/storeImage/dto/StoreImageUploadResponse.java (1)

8-13: 프로젝트 DTO 컨벤션과의 일관성을 위해 어노테이션 추가를 고려해보세요

코드 구현이 깔끔하고 잘 작성되었습니다. 다만 프로젝트의 다른 DTO들(예: UserResponseDto)과의 일관성을 위해 @RequiredArgsConstructor 어노테이션 추가를 고려해보시면 좋겠어요.

 import lombok.Builder;
 import lombok.Getter;
+import lombok.RequiredArgsConstructor;

 @Getter
 @Builder
+@RequiredArgsConstructor
 public class StoreImageUploadResponse {

이렇게 하면 프로젝트 전체의 DTO 스타일이 더욱 일관되게 유지됩니다.

src/main/java/com/example/gtable/storeImage/model/StoreImage.java (1)

35-42: 컬럼 제약조건을 세밀하게 조정해 주세요.

필드 정의는 잘 되어 있지만, 실제 사용 시나리오를 고려한 미세 조정이 필요해 보입니다.

다음과 같은 개선을 고려해 주세요:

-@Column(nullable = false, length = 500)
+@Column(nullable = false, length = 2000)  // S3 URL이 길어질 수 있음
 private String imageUrl;

-@Column(nullable = false, length = 500)
+@Column(nullable = false, length = 1000)  // S3 키도 경로가 깊어질 수 있음
 private String fileKey;

-@Column(length = 20)
+@Column(length = 50)  // 이미지 타입이 다양할 수 있음
 private String type;

또한 type 필드에 대한 enum 값 검증을 추가하시는 것도 좋겠습니다:

@Enumerated(EnumType.STRING)
@Column(length = 50)
private ImageType type;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d890665 and b38796b.

📒 Files selected for processing (17)
  • build.gradle (1 hunks)
  • src/main/java/com/example/gtable/global/config/AsyncConfig.java (1 hunks)
  • src/main/java/com/example/gtable/global/config/AwsS3Config.java (1 hunks)
  • src/main/java/com/example/gtable/global/config/SecurityConfig.java (1 hunks)
  • src/main/java/com/example/gtable/global/s3/S3Service.java (1 hunks)
  • src/main/java/com/example/gtable/store/dto/StoreCreateRequest.java (0 hunks)
  • src/main/java/com/example/gtable/store/dto/StoreCreateResponse.java (0 hunks)
  • src/main/java/com/example/gtable/store/dto/StoreReadDto.java (2 hunks)
  • src/main/java/com/example/gtable/store/dto/StoreReadResponse.java (1 hunks)
  • src/main/java/com/example/gtable/store/dto/StoreUpdateRequest.java (0 hunks)
  • src/main/java/com/example/gtable/store/model/Store.java (1 hunks)
  • src/main/java/com/example/gtable/store/service/StoreServiceImpl.java (6 hunks)
  • src/main/java/com/example/gtable/storeImage/controller/StoreImageController.java (1 hunks)
  • src/main/java/com/example/gtable/storeImage/dto/StoreImageUploadResponse.java (1 hunks)
  • src/main/java/com/example/gtable/storeImage/model/StoreImage.java (1 hunks)
  • src/main/java/com/example/gtable/storeImage/repository/StoreImageRepository.java (1 hunks)
  • src/main/java/com/example/gtable/storeImage/service/StoreImageService.java (1 hunks)
💤 Files with no reviewable changes (3)
  • src/main/java/com/example/gtable/store/dto/StoreUpdateRequest.java
  • src/main/java/com/example/gtable/store/dto/StoreCreateRequest.java
  • src/main/java/com/example/gtable/store/dto/StoreCreateResponse.java
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/main/java/com/example/gtable/storeImage/dto/StoreImageUploadResponse.java (1)
src/main/java/com/example/gtable/user/dto/UserResponseDto.java (1)
  • Builder (9-18)
🔇 Additional comments (26)
src/main/java/com/example/gtable/store/dto/StoreReadResponse.java (1)

17-22: 좋은 리팩토링입니다!

fromEntity에서 of로 메서드명을 변경한 것은 훌륭한 개선이에요. of는 정적 팩토리 메서드의 표준적인 네이밍 컨벤션이며, 더 간결하고 명확합니다. Joshua Bloch의 Effective Java에서도 권장하는 패턴이니까 코드의 가독성과 일관성이 향상될 것 같아요.

src/main/java/com/example/gtable/global/config/AsyncConfig.java (1)

9-21:

Details

✅ Verification successful

S3 업로드를 위한 비동기 설정이 잘 구현되었습니다

Bulkhead 패턴을 적절히 적용하여 동시 이미지 업로드 요청으로 인한 스레드 고갈 문제를 해결하는 좋은 접근입니다. 스레드 이름 프리픽스 설정과 초기화 과정도 올바르게 구현되어 있어요.

다만 현재 설정된 스레드 풀 매개변수들(코어: 5, 최대: 10, 큐: 100)이 실제 운영 환경에서 적절한지 확인이 필요할 것 같습니다.

다음 스크립트로 유사한 설정을 사용하는 다른 프로젝트 사례를 확인해보세요:


🏁 Script executed:

#!/bin/bash
# 프로젝트 내 다른 ThreadPoolTaskExecutor 설정 확인
rg -A 10 -B 5 "ThreadPoolTaskExecutor|setCorePoolSize|setMaxPoolSize"

Length of output: 1948


AsyncConfig 설정 독립성 확인 및 승인

프로젝트 내 다른 ThreadPoolTaskExecutor 설정을 사용하는 코드는 없으며, 해당 설정이 유일하게 적용되고 있음을 확인했습니다.
스레드 풀 매개변수(corePoolSize=5, maxPoolSize=10, queueCapacity=100)는 S3 업로드 부하 특성에 맞춰 운영 환경에서 모니터링 후 필요 시 조정해 주세요.

• 검증 대상 파일:
– src/main/java/com/example/gtable/global/config/AsyncConfig.java

해당 구현은 그대로 승인하며, 추가 코드 변경은 필요 없습니다.

src/main/java/com/example/gtable/global/config/AwsS3Config.java (1)

15-22: AWS 자격 증명 관리에 대한 보안 고려사항을 확인해주세요

자격 증명을 application.properties에서 직접 주입하는 방식은 개발 환경에서는 괜찮지만, 운영 환경에서는 보안상 취약할 수 있습니다. AWS IAM 역할이나 환경 변수를 통한 자격 증명 관리를 고려해보시는 것이 좋겠어요.

src/main/java/com/example/gtable/storeImage/dto/StoreImageUploadResponse.java (1)

15-21: static factory method 패턴이 잘 적용되었습니다

fromEntity 메서드가 엔티티에서 DTO로의 변환을 깔끔하게 처리하고 있어요. 빌더 패턴과 함께 사용하여 가독성과 유지보수성이 좋습니다.

src/main/java/com/example/gtable/store/dto/StoreReadDto.java (3)

4-4: 새로운 다중 이미지 지원을 위한 import가 적절히 추가되었습니다

단일 이미지에서 다중 이미지 구조로의 전환을 위해 필요한 import들이 정확하게 추가되었어요.

Also applies to: 7-7


22-22: 단일 이미지에서 다중 이미지로의 아키텍처 개선이 잘 반영되었습니다

String storeImageUrl에서 List<StoreImageUploadResponse> images로의 변경이 새로운 이미지 관리 시스템의 요구사항을 적절히 반영하고 있습니다. 필드명도 더 명확하고 의미있게 변경되었어요.


27-27: fromEntity 메서드의 시그니처 변경이 논리적으로 일관됩니다

다중 이미지 지원을 위해 fromEntity 메서드에 List<StoreImageUploadResponse> images 파라미터를 추가하고, 빌더에서 이를 적절히 설정하는 방식이 매우 깔끔합니다. 기존 단일 이미지 구조에서 다중 이미지 구조로의 전환이 잘 구현되었어요.

Also applies to: 37-37

src/main/java/com/example/gtable/global/s3/S3Service.java (2)

18-27: 클래스 설계가 잘 구성되어 있습니다!

의존성 주입과 record 사용으로 깔끔한 구조를 만드셨네요. S3UploadResult record는 간결하고 immutable한 데이터 전달 객체로 적절합니다.


54-56: 파일 키 생성 로직이 잘 구현되어 있습니다!

UUID 사용으로 파일명 중복을 방지하고, store 별로 디렉토리를 구분하는 것이 좋은 설계입니다.

src/main/java/com/example/gtable/storeImage/controller/StoreImageController.java (2)

21-27: 컨트롤러 구조가 깔끔하게 설계되어 있습니다!

@RequestMapping으로 공통 경로를 설정하고 의존성 주입을 적절히 사용하셨네요.


44-55: 삭제 엔드포인트 구현이 적절합니다!

HTTP 상태 코드 204 No Content 사용과 표준화된 응답 구조가 REST 규칙에 잘 맞습니다.

src/main/java/com/example/gtable/storeImage/service/StoreImageService.java (2)

20-27: 서비스 클래스 구조가 잘 설계되어 있습니다!

필요한 의존성들이 적절히 주입되어 있고, Repository 패턴과 S3Service 연동이 깔끔합니다.


60-67: 삭제 메서드 구현이 적절합니다!

엔티티 조회 후 S3와 DB에서 순차적으로 삭제하는 로직이 올바릅니다. @transactional 적용도 적절합니다.

src/main/java/com/example/gtable/storeImage/model/StoreImage.java (2)

20-25: 엔티티 클래스 설계가 훌륭합니다!

Lombok 어노테이션 사용과 BaseTimeEntity 상속, 적절한 테이블 명명이 모범적입니다. @SuperBuilder 사용으로 상속 구조에서도 Builder 패턴을 잘 지원하고 있습니다.


27-33: 기본키와 연관관계 설정이 적절합니다!

IDENTITY 전략 사용과 지연 로딩 설정이 좋습니다. Store와의 ManyToOne 관계도 올바르게 매핑되어 있습니다.

src/main/java/com/example/gtable/store/model/Store.java (4)

45-46: 생성자 시그니처 변경이 적절합니다.

단일 이미지 URL에서 다중 이미지 지원으로 전환하면서 생성자에서 storeImageUrl 파라미터를 제거한 것은 올바른 설계 변경입니다. 이미지 관리가 별도의 엔티티로 분리되어 관심사의 분리 원칙을 잘 따르고 있습니다.


57-61: 정보 업데이트 메서드 통합이 우수합니다.

개별 setter 메서드들을 updateInfo 메서드로 통합한 것은 매우 좋은 설계입니다. 이는 다음과 같은 장점을 제공합니다:

  • 원자적 업데이트 보장
  • 불변성 원칙 준수
  • 도메인 로직의 명확한 캡슐화

메서드명도 의도를 명확히 드러내어 가독성이 뛰어납니다.


63-65: 소프트 삭제 메서드가 명확합니다.

markAsDeleted() 메서드는 소프트 삭제 패턴을 명확히 표현하고 있습니다. 메서드명이 의도를 잘 드러내며, 도메인 로직을 엔티티 내부에 캡슐화하여 객체지향 설계 원칙을 잘 따르고 있습니다.


67-73: 상태 관리 메서드들이 잘 설계되었습니다.

activate()deactivate() 메서드는 매장의 활성화 상태를 명확하게 관리할 수 있게 해줍니다. 메서드명이 직관적이며, boolean 값을 직접 설정하는 것보다 의미가 명확합니다. 이러한 접근 방식은 도메인 주도 설계(DDD)의 모범 사례를 따르고 있습니다.

src/main/java/com/example/gtable/store/service/StoreServiceImpl.java (7)

15-17: 이미지 관련 의존성 추가가 적절합니다.

새로운 이미지 기능을 위한 import 문들이 올바르게 추가되었습니다. 패키지 구조가 명확하고 관심사의 분리가 잘 되어 있어 보입니다.


27-27: StoreImageRepository 의존성 주입이 올바릅니다.

StoreImageRepository를 final 필드로 선언하여 불변성을 보장하고, @RequiredArgsConstructor를 통한 생성자 주입 방식을 사용하는 것이 Spring의 모범 사례를 잘 따르고 있습니다.


56-56: 메서드명 변경이 일관성을 개선했습니다.

fromEntity에서 of로 메서드명을 변경한 것은 정적 팩토리 메서드의 일반적인 네이밍 컨벤션을 따르는 좋은 개선입니다.


65-70: 단일 매장 조회에서도 동일한 패턴 적용이 좋습니다.

단일 매장 조회에서도 이미지 데이터를 포함하는 일관된 패턴을 적용한 것이 좋습니다. 코드의 일관성과 예측 가능성을 높여줍니다.


79-83: 엔티티의 통합 업데이트 메서드 활용이 우수합니다.

updateInfo 메서드를 사용하여 매장 정보를 업데이트하는 것은 엔티티의 캡슐화된 로직을 적절히 활용하는 좋은 예시입니다. 코드가 더 명확하고 유지보수하기 쉬워졌습니다.


87-92: 업데이트 응답에 이미지 포함이 일관성을 높입니다.

업데이트 후 응답에도 이미지 데이터를 포함하는 것은 API의 일관성을 높이는 좋은 접근입니다. 클라이언트가 추가 요청 없이 최신 상태를 확인할 수 있어 효율적입니다.


101-101: 명시적인 삭제 메서드 사용이 훌륭합니다.

markAsDeleted() 메서드를 사용하여 소프트 삭제를 수행하는 것은 의도를 명확히 표현하는 좋은 방법입니다. 도메인 로직이 엔티티에 잘 캡슐화되어 있습니다.

Comment on lines +29 to +44
@Bulkhead(name = "s3UploadBulkhead", type = Bulkhead.Type.THREADPOOL)
@Async("s3UploadExecutor")
public CompletableFuture<S3UploadResult> upload(Long storeId, MultipartFile file) {
try (InputStream inputStream = file.getInputStream()) {
String key = createFileKey(storeId, file.getOriginalFilename());
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(file.getSize());

amazonS3Client.putObject(bucket, key, inputStream, metadata);
String url = amazonS3Client.getUrl(bucket, key).toString();

return CompletableFuture.completedFuture(new S3UploadResult(key, url));
} catch (Exception e) {
throw new RuntimeException("S3 업로드 실패", e);
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

비동기 업로드 메서드의 에러 처리와 리소스 관리를 개선해 주세요.

Bulkhead 패턴과 비동기 처리 적용은 좋습니다! 하지만 몇 가지 개선이 필요해 보입니다:

  1. try-with-resources 문에서 예외 발생 시 CompletableFuture.completedFuture 대신 CompletableFuture.failedFuture를 반환해야 합니다.
  2. 일반적인 RuntimeException보다 구체적인 예외 타입을 사용하는 것이 좋겠습니다.

다음과 같이 개선해 주세요:

 @Bulkhead(name = "s3UploadBulkhead", type = Bulkhead.Type.THREADPOOL)
 @Async("s3UploadExecutor")
 public CompletableFuture<S3UploadResult> upload(Long storeId, MultipartFile file) {
 	try (InputStream inputStream = file.getInputStream()) {
 		String key = createFileKey(storeId, file.getOriginalFilename());
 		ObjectMetadata metadata = new ObjectMetadata();
 		metadata.setContentLength(file.getSize());

 		amazonS3Client.putObject(bucket, key, inputStream, metadata);
 		String url = amazonS3Client.getUrl(bucket, key).toString();

 		return CompletableFuture.completedFuture(new S3UploadResult(key, url));
 	} catch (Exception e) {
-		throw new RuntimeException("S3 업로드 실패", e);
+		return CompletableFuture.failedFuture(new S3UploadException("S3 업로드 실패: " + file.getOriginalFilename(), e));
 	}
 }
🤖 Prompt for AI Agents
In src/main/java/com/example/gtable/global/s3/S3Service.java around lines 29 to
44, the asynchronous upload method currently throws a RuntimeException on error
instead of returning a failed CompletableFuture, which is inconsistent with
async error handling. Modify the catch block to return
CompletableFuture.failedFuture with a more specific exception type related to S3
upload failure. This ensures proper asynchronous error propagation and clearer
exception semantics.

Comment on lines +45 to +51
.map(store -> {
List<StoreImage> images = storeImageRepository.findByStore(store);
List<StoreImageUploadResponse> imageDto = images.stream()
.map(StoreImageUploadResponse::fromEntity)
.toList();
return StoreReadDto.fromEntity(store, imageDto);
})
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

이미지 데이터 통합 로직의 성능 최적화를 고려해보세요.

각 매장마다 개별적으로 이미지를 조회하는 방식은 N+1 쿼리 문제를 야기할 수 있습니다. 매장 수가 많아질 경우 성능에 영향을 줄 수 있습니다.

다음과 같은 개선을 고려해보시기 바랍니다:

// 배치 조회를 위한 개선된 방법
List<Long> storeIds = stores.stream()
    .map(Store::getStoreId)
    .toList();
Map<Long, List<StoreImage>> imagesByStoreId = storeImageRepository
    .findByStoreIdIn(storeIds)
    .stream()
    .collect(Collectors.groupingBy(image -> image.getStore().getStoreId()));

List<StoreReadDto> storeRead = stores.stream()
    .map(store -> {
        List<StoreImage> images = imagesByStoreId.getOrDefault(store.getStoreId(), List.of());
        List<StoreImageUploadResponse> imageDto = images.stream()
            .map(StoreImageUploadResponse::fromEntity)
            .toList();
        return StoreReadDto.fromEntity(store, imageDto);
    })
    .toList();
🤖 Prompt for AI Agents
In src/main/java/com/example/gtable/store/service/StoreServiceImpl.java around
lines 45 to 51, the current code fetches images for each store individually
causing N+1 query issues. To fix this, first collect all store IDs, then fetch
all images for these stores in a single batch query using a repository method
like findByStoreIdIn. Group the images by store ID into a map, and then when
mapping stores to DTOs, retrieve the images from this map instead of querying
per store. This reduces the number of database queries and improves performance.

@Jjiggu Jjiggu merged commit c7b4e21 into develop Jun 16, 2025
1 check passed
@coderabbitai coderabbitai bot mentioned this pull request Jun 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[백엔드] 주점 CRUD 개발

2 participants