-
Notifications
You must be signed in to change notification settings - Fork 0
[Refactor] 응원 등록 이미지 트랜잭션 분리 및 보상 트랜잭션 적용 #216
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
Conversation
WalkthroughCheer 등록 흐름을 CheerRegisterFacade로 분리하고, FileClient의 비동기 파일 이동을 동기 단일 스레드로 전환했으며 이미지 이동 실패 시 파일 및 DB 롤백(보상 트랜잭션)을 추가했습니다. 서비스는 CheerCreationResult 반환으로 분리되고 테스트 인프라가 업데이트되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
actor Client
participant CheerController
participant CheerRegisterFacade
participant CheerService
participant FileClient
participant Repository
Client->>CheerController: POST /cheers (request)
CheerController->>CheerRegisterFacade: registerCheer(request, result, memberId, domain)
CheerRegisterFacade->>CheerService: createCheer(request, result, memberId)
CheerService->>Repository: save(cheer)
CheerService-->>CheerRegisterFacade: CheerCreationResult
rect rgb(200,220,255)
Note over CheerRegisterFacade,FileClient: 동기 이미지 이동 처리
CheerRegisterFacade->>FileClient: moveTempFilesToPermanent(tempKeys)
FileClient-->>CheerRegisterFacade: movedKeys / exception
end
alt 이동 성공
CheerRegisterFacade->>CheerService: saveCheerImages(cheerId, images, movedKeys)
CheerService->>Repository: save(cheerImages)
CheerRegisterFacade-->>CheerController: CheerResponse
CheerController->>Client: 200 OK
else 이동 중 예외
rect rgb(255,200,200)
Note over CheerRegisterFacade,FileClient: 롤백: 삭제 및 DB 정리
CheerRegisterFacade->>FileClient: deleteFiles(movedKeys)
CheerRegisterFacade->>CheerService: deleteCheer(cheerId)
CheerService->>Repository: delete(cheer)
end
CheerRegisterFacade-->>CheerController: throw BusinessException(FAIL_TEMP_IMAGE_PROCESS)
CheerController->>Client: Error Response
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🧹 Nitpick comments (4)
src/main/java/eatda/client/file/FileClient.java (1)
53-72: 멱등성 고려사항현재 구현은 동일한 파일을 두 번 이동하려고 하면 실패합니다(소스 키가 이미 삭제됨). 재시도 시나리오나 네트워크 실패 후 복구 시 이는 문제가 될 수 있습니다.
필요한 경우, 대상 키가 이미 존재하는지 확인하거나
copyObject전에 소스 키의 존재 여부를 확인하여 멱등성을 개선할 수 있습니다. 다만 현재 파사드 계층에서 재시도를 하지 않는다면 이는 추후 개선사항으로 미뤄도 됩니다.src/test/java/eatda/facade/cheer/CheerRegisterFacadeTest.java (2)
121-132: 예외 발생을 명시적으로 검증하는 것을 권장합니다.현재 try-catch로 예외를 무시하고 있어, 예외가 발생하지 않아도 테스트가 통과할 수 있습니다.
assertThrows를 사용하면 예외 발생과 롤백을 모두 검증할 수 있습니다.- try { - cheerRegisterFacade.registerCheer( - request, - storeResult, - member.getId(), - ImageDomain.CHEER - ); - } catch (Exception ignored) { - } + assertThrows(Exception.class, () -> + cheerRegisterFacade.registerCheer( + request, + storeResult, + member.getId(), + ImageDomain.CHEER + ) + ); assertThat(cheerRepository.count()).isZero();
257-295: 헬퍼 메서드 네이밍을 더 명확하게 하는 것을 권장합니다.
getRegisterRequest()와getCheerRegisterRequest()가 매우 유사한 이름이지만 다른 목적으로 사용됩니다. 의도를 명확히 하면 가독성이 향상됩니다.예시:
getRegisterRequest()→createTwoImageRequest()getCheerRegisterRequest()→createThreeImageRequest()src/main/java/eatda/service/cheer/CheerService.java (1)
71-91:sortedImages와permanentKeys크기 불일치 시 예외가 발생할 수 있습니다.
IntStream.range(0, sortedImages.size())로 순회하면서permanentKeys.get(i)에 접근하므로, 두 리스트의 크기가 다르면IndexOutOfBoundsException이 발생합니다. 현재 Facade에서 동일한 리스트를 사용하므로 문제없지만, 방어적 검증을 추가하면 안전합니다.@Transactional public void saveCheerImages(Long cheerId, List<CheerRegisterRequest.UploadedImageDetail> sortedImages, List<String> permanentKeys) { + if (sortedImages.size() != permanentKeys.size()) { + throw new IllegalArgumentException("이미지 정보와 키 목록의 크기가 일치하지 않습니다."); + } + Cheer cheer = cheerRepository.findById(cheerId) .orElseThrow(() -> new BusinessException(BusinessErrorCode.CHEER_NOT_FOUND));
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
src/main/java/eatda/client/file/FileClient.java(2 hunks)src/main/java/eatda/controller/cheer/CheerController.java(2 hunks)src/main/java/eatda/controller/cheer/CheerResponse.java(1 hunks)src/main/java/eatda/exception/BusinessErrorCode.java(1 hunks)src/main/java/eatda/facade/CheerCreationResult.java(1 hunks)src/main/java/eatda/facade/CheerRegisterFacade.java(1 hunks)src/main/java/eatda/service/cheer/CheerService.java(8 hunks)src/test/java/eatda/document/BaseDocumentTest.java(2 hunks)src/test/java/eatda/document/cheer/CheerDocumentTest.java(2 hunks)src/test/java/eatda/facade/BaseFacadeTest.java(1 hunks)src/test/java/eatda/facade/cheer/CheerRegisterFacadeTest.java(1 hunks)src/test/java/eatda/service/cheer/CheerServiceTest.java(9 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/eatda/client/file/FileClient.java (1)
src/main/java/eatda/facade/CheerRegisterFacade.java (1)
Slf4j(17-76)
⏰ 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 (19)
src/main/java/eatda/exception/BusinessErrorCode.java (1)
26-32:CHEER_NOT_FOUND추가는 적절합니다.
CHE005코드는 현재 이 정의 한 곳에서만 사용되며,CheerService의 두 위치(77번, 137번 줄)에서 Cheer 엔티티 조회 실패 시에만 던져집니다.HttpStatus.NOT_FOUND로 설정되어 있어 404 응답이 명확하게 표현되므로, 에러 코드 중복도 없고 의미도 정확하게 일치합니다.src/test/java/eatda/document/cheer/CheerDocumentTest.java (1)
102-102: LGTM! 파사드 기반 아키텍처로의 전환이 올바르게 반영되었습니다.테스트가 새로운
CheerRegisterFacade를 올바르게 모킹하고 있으며, 성공 및 실패 시나리오 모두 적절하게 커버하고 있습니다.Also applies to: 140-140
src/test/java/eatda/document/BaseDocumentTest.java (1)
10-10: LGTM! 테스트 베이스 클래스에 파사드 모킹 지원이 추가되었습니다.새로운
CheerRegisterFacade의존성이 문서 테스트에서 모킹 가능하도록 올바르게 추가되었습니다.Also applies to: 70-71
src/main/java/eatda/controller/cheer/CheerController.java (1)
9-9: LGTM! 컨트롤러가 파사드 패턴으로 올바르게 위임하도록 개선되었습니다.컨트롤러가 복잡한 다단계 플로우를 직접 오케스트레이션하는 대신
CheerRegisterFacade에 위임하는 것은 관심사 분리 측면에서 좋은 접근입니다. 파사드 계층에서 트랜잭션 경계와 보상 트랜잭션을 처리할 수 있게 되었습니다.Also applies to: 33-33, 40-40
src/test/java/eatda/facade/BaseFacadeTest.java (1)
1-36: LGTM! 파사드 테스트를 위한 베이스 클래스가 잘 구성되었습니다.외부 의존성(OauthClient, MapClient, FileClient)을 적절히 모킹하고, 필요한 생성자와 리포지토리를 주입하며, DatabaseCleaner를 통해 테스트 격리를 보장합니다.
NONE웹 환경 설정도 파사드 레이어 테스트에 적합합니다.src/main/java/eatda/facade/CheerCreationResult.java (1)
1-7: LGTM! 결과 타입의 명확한 캡슐화입니다.
CheerCreationResult레코드는 서비스 계층이 컨트롤러 응답 타입에 결합되지 않고 도메인 엔티티를 반환할 수 있게 해줍니다. 깔끔한 관심사 분리입니다.src/test/java/eatda/service/cheer/CheerServiceTest.java (2)
19-19: LGTM! 테스트가 새로운 서비스 시그니처에 맞춰 올바르게 업데이트되었습니다.
CheerCreationResult기반의 새로운 플로우를 반영하여 테스트가 잘 수정되었습니다. 모든 테스트 시나리오가 여전히 적절하게 커버되고 있으며, 멀티라인 생성자 형식과var사용으로 가독성도 향상되었습니다.Also applies to: 47-62, 70-85, 88-119, 122-145, 148-165, 168-188
296-309: LGTM! 헬퍼 메서드로 테스트 코드 중복이 제거되었습니다.
storeSearchResult()헬퍼 메서드는 테스트 전반에서 반복되는StoreSearchResult생성 로직을 효과적으로 제거합니다.src/main/java/eatda/client/file/FileClient.java (1)
23-23: LGTM! 매직 스트링 제거로 코드 유지보수성이 향상되었습니다.
PATH_DELIMITER상수를 도입하여 경로 구분자를 일관되게 사용하고 있습니다.Also applies to: 59-59, 82-83
src/main/java/eatda/facade/CheerRegisterFacade.java (3)
57-62: LGTM!
sortImages헬퍼 메서드가 명확하고 간결하게 구현되어 있습니다.
64-75: LGTM!파일 이동 로직이
FileClient에 적절히 위임되어 있으며,FileClient내부에서 부분 실패 시 이미 이동된 파일을 정리하는 것을 확인했습니다.
25-55: 파일 I/O와 DB 트랜잭션의 분리가 잘 구현되었습니다.PR 목표대로
createCheer가 먼저 커밋되고, 이미지 이동은 트랜잭션 외부에서 수행되며, 실패 시 보상 트랜잭션으로 정리하는 설계가 적절합니다.src/test/java/eatda/facade/cheer/CheerRegisterFacadeTest.java (3)
36-81: LGTM!정상 경로 테스트가 잘 작성되어 있습니다. 응답 검증과 mock 상호작용 검증이 모두 포함되어 있습니다.
134-174: 테스트명과 실제 동작의 차이를 확인해 주세요.테스트명은 "부분적으로 성공한 후 실패"를 의미하지만,
FileClient.moveTempFilesToPermanent는 내부적으로 부분 실패 시 이미 이동된 파일을 정리합니다. Facade 입장에서는 부분 성공 여부를 알 수 없으므로,이미지_이동_중_실패하면_응원을_삭제한다테스트와 동일한 시나리오를 검증하고 있습니다.테스트 의도에 따라 중복 테스트를 제거하거나, 테스트명을 수정하는 것을 고려해 주세요.
218-255: LGTM!이미지가 없는 경우의 테스트가 잘 작성되어 있습니다.
Mockito.never()를 사용하여 파일 이동이 호출되지 않음을 검증하는 것이 적절합니다.src/main/java/eatda/service/cheer/CheerService.java (4)
47-60: LGTM!
createCheer가CheerCreationResult를 반환하도록 리팩토링되어 이미지 처리가 Facade로 분리되었습니다. 책임 분리가 명확합니다.
129-132: LGTM!보상 트랜잭션을 위한
deleteCheer메서드가 간결하게 구현되어 있습니다.
134-140: LGTM!단일 응원 조회 메서드가 명확하게 구현되어 있습니다.
107-115: LGTM!응답 매핑 로직이 간결하게 리팩토링되었습니다.
There was a problem hiding this 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
♻️ Duplicate comments (2)
src/test/java/eatda/facade/cheer/CheerRegisterFacadeTest.java (2)
202-217: 이전 리뷰에서 지적된 문제가 여전히 남아있습니다.이 코드 영역은 이전 리뷰 코멘트에서 이미 지적되었지만 수정되지 않았습니다. try-catch 블록 외부의 assertion과 verify로 인해 예외가 발생하지 않으면 테스트가 false positive로 통과할 수 있습니다.
다음 diff를 적용하여 예외 발생을 명시적으로 검증하세요:
- try { - cheerRegisterFacade.registerCheer( - request, - storeResult, - member.getId(), - ImageDomain.CHEER - ); - } catch (Exception ignored) { - } - - assertThat(cheerRepository.count()) - .as("DB 에러(컬럼 길이 초과) 발생 시 응원글은 삭제되어야 한다.") - .isZero(); - - verify(fileClient).deleteFiles(movedKeys); + assertThrows(Exception.class, () -> + cheerRegisterFacade.registerCheer( + request, + storeResult, + member.getId(), + ImageDomain.CHEER + ) + ); + + assertThat(cheerRepository.count()) + .as("DB 에러(컬럼 길이 초과) 발생 시 응원글은 삭제되어야 한다.") + .isZero(); + + verify(fileClient).deleteFiles(movedKeys);
161-174: try-catch 블록 외부의 assertion으로 인한 false positive 위험이 있습니다.예외가 발생하지 않아도 테스트가 통과하므로, 부분 성공 후 실패 시 보상 트랜잭션이 제대로 동작하지 않아도 테스트가 성공할 수 있습니다.
다음 diff를 적용하여 예외 발생을 명시적으로 검증하세요:
- try { - cheerRegisterFacade.registerCheer( - request, - storeResult, - member.getId(), - ImageDomain.CHEER - ); - } catch (Exception ignored) { - } - - assertThat(cheerRepository.count()) - .as("부분 성공 후 실패 시 Cheer는 삭제되어야 한다.") - .isZero(); + assertThrows(SdkException.class, () -> + cheerRegisterFacade.registerCheer( + request, + storeResult, + member.getId(), + ImageDomain.CHEER + ) + ); + + assertThat(cheerRepository.count()) + .as("부분 성공 후 실패 시 Cheer는 삭제되어야 한다.") + .isZero();
🧹 Nitpick comments (1)
src/test/java/eatda/facade/cheer/CheerRegisterFacadeTest.java (1)
258-296: 헬퍼 메서드 이름이 명확하게 구분되지 않습니다.
getRegisterRequest()와getCheerRegisterRequest()는 유사한 이름으로 각 메서드가 어떤 시나리오에 사용되는지 명확하지 않습니다. 특히getCheerRegisterRequest()는 부분 성공 테스트에만 사용되므로, 용도를 반영한 이름으로 변경하는 것을 고려해보세요.예시:
- private CheerRegisterRequest getRegisterRequest() { + private CheerRegisterRequest createRequestWithTwoImages() { // ... } - private CheerRegisterRequest getCheerRegisterRequest() { + private CheerRegisterRequest createRequestWithThreeImages() { // ... }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/test/java/eatda/facade/cheer/CheerRegisterFacadeTest.java(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/test/java/eatda/facade/cheer/CheerRegisterFacadeTest.java (1)
src/test/java/eatda/document/cheer/CheerDocumentTest.java (3)
Nested(42-154)Nested(156-241)Nested(243-312)
⏰ 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 (2)
src/test/java/eatda/facade/cheer/CheerRegisterFacadeTest.java (2)
36-81: 정상 시나리오 테스트가 잘 작성되었습니다.응원 등록 성공 케이스를 명확하게 검증하고 있으며, 이미지 이동과 응답 데이터 검증이 적절합니다.
219-256: 이미지 없는 등록 케이스가 적절하게 검증되었습니다.엣지 케이스를 잘 다루고 있으며, fileClient 호출이 없음을
Mockito.never()로 명확하게 검증하고 있습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (3)
src/test/java/eatda/facade/cheer/CheerRegisterFacadeTest.java (3)
134-173: 테스트 이름과 실제 동작이 일치하지 않습니다."부분적으로 성공한 후 실패"를 테스트한다고 명시되어 있지만, mock 설정(lines 153-159)이 즉시 예외를 던지므로 실제 부분 성공 시나리오를 검증하지 못합니다. 이전 테스트(
이미지_이동_중_실패하면_응원을_삭제한다)와 본질적으로 동일한 동작을 테스트합니다.부분 성공 시나리오를 실제로 테스트하려면:
fileClient.moveTempFilesToPermanent가 일부 키를 반환한 후 내부적으로 실패하는 상황을 시뮬레이션하거나- 파일 이동 후 후속 작업에서 실패하는 시나리오를 구성해야 합니다.
만약
FileClient내부에서 부분 성공/롤백을 처리한다면, 해당 로직은FileClient단위 테스트에서 검증하는 것이 더 적합합니다.
201-208: 예외 타입이 너무 광범위합니다.
Exception.class는 너무 포괄적이어서 의도하지 않은 예외도 테스트를 통과시킬 수 있습니다. DB 제약 조건 위반에 대한 구체적인 예외 타입(예:DataIntegrityViolationException또는ConstraintViolationException)을 사용하면 테스트의 정확성이 향상됩니다.- assertThrows(Exception.class, () -> + assertThrows(DataIntegrityViolationException.class, () -> cheerRegisterFacade.registerCheer( request, storeResult, member.getId(), ImageDomain.CHEER ) );
257-295: 헬퍼 메서드 통합을 고려해 보세요.
getRegisterRequest()와getCheerRegisterRequest()메서드가 유사한 구조를 가지고 있습니다. 이미지 개수와 설명을 파라미터로 받는 단일 헬퍼 메서드로 통합하면 중복을 줄이고 테스트 의도를 더 명확히 표현할 수 있습니다.@NonNull private CheerRegisterRequest createRegisterRequest(String description, int imageCount) { List<CheerRegisterRequest.UploadedImageDetail> images = IntStream.rangeClosed(1, imageCount) .mapToObj(i -> new CheerRegisterRequest.UploadedImageDetail( "temp/key" + i + ".jpg", (long) i, "image/jpeg", 1000L )) .toList(); return new CheerRegisterRequest( "kakao-1", "농민백암순대", description, images, List.of(CheerTagName.GOOD_FOR_DATING) ); }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/test/java/eatda/facade/cheer/CheerRegisterFacadeTest.java(1 hunks)
⏰ 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 (4)
src/test/java/eatda/facade/cheer/CheerRegisterFacadeTest.java (4)
1-33: 클래스 구조 및 임포트가 적절합니다.테스트 클래스 설정이 적절하며, BaseFacadeTest를 확장하여 필요한 의존성을 상속받고 있습니다.
37-82: 성공 시나리오 테스트가 잘 구성되어 있습니다.응답 내용 검증과 mock 상호작용 검증이 적절히 포함되어 있습니다.
84-132: 이전 리뷰 피드백이 반영되어 assertThrows를 사용하고 있습니다.예외 발생을 명시적으로 검증하고, 이후 cleanup 검증을 수행하는 구조로 개선되었습니다.
218-255: 이미지 없는 경우의 엣지 케이스가 잘 테스트되었습니다.이미지가 없을 때 파일 이동이 호출되지 않음을 검증하고, 응원이 정상적으로 등록됨을 확인하는 적절한 테스트입니다.
|
|
🎉 This PR is included in version 1.8.0-develop.20 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
|
🎉 This PR is included in version 1.9.4 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |



✨ 개요
🧾 관련 이슈
Close #213
🔍 참고 사항 (선택)
Summary by CodeRabbit
새로운 기능
개선 사항
버그 픽스
테스트
✏️ Tip: You can customize this high-level summary in your review settings.