Skip to content

Conversation

@zyovn
Copy link
Member

@zyovn zyovn commented Oct 20, 2025

🔗 연관된 이슈

🚀 변경 유형

  • ✨ 기능 추가 (feature)
  • 🐛 버그 수정 (fix)
  • 📝 문서 변경 (docs)
  • ♻️ 리팩토링 (refactor)
  • 🧪 테스트 추가 / 수정 (test)
  • ⚙️ 설정 변경 (chore)

📝 작업 내용

  • 앨범 생성 API
    • 일주일 내 3개 제한
    • 인원 수 최대 64명 제한
  • 앨범 코드 관련 벤치마크 테스트

📸 스크린샷

앨범 생성

스크린샷 2025-10-20 203956

필수 약관 미동의

스크린샷 2025-10-20 195311

썸네일 이미지 미선택

image

잘못된 앨범 인원 수 (0명 이하 65명 이상)

스크린샷 2025-10-20 195300

일주일 내 앨범 3개 초과 생성 (4번째 앨범을 생성하려고 할 경우)

image

행사 날짜가 미래일 경우

image

행사 날짜 미입력

image

uuid v4 vs uuid v7 db 성능 벤치마크 테스트

image

-> 생각보다 insert에서 속도가 크게 차이가 나서 v7으로 코드 생성 로직 구현했습니다!

💬 리뷰 요구사항

  • 늦어져서 죄송합니다...
  • 앨범 관련 비즈니스 로직이 점점 많아지는 느낌이라 나중에 쪼개야 할 것 같기도 합니다!
  • swagger 경로를 세분화 했습니닷 이거 관련해서 yml 수정되었으니 참고 부탁 드립니다! (인텔리제이에서 경고 뜨ㅣ우긴 한데 걍 무시해도 됩니다 ㅎ 잘 되는 거 확인햇어야)
  • 궁금한 점
    • 앨범에서 maxPhotoCount는 앨범 당 최대 업로드 가능한 사진 수 의미하는 게 맞을까요?! 올릴 수 있는 사진은 모든 앨범마다 2000장으로 고정되어 있는 걸로 알고 있는데 그럼 해당 컬럼이 필요할까 싶어서 여쭤봅니당

Summary by CodeRabbit

  • 새로운 기능

    • 앨범 생성 API 추가 — 요청 필드(테마 이모지, 제목, 인원, 이벤트 날짜, 약관 동의) 및 생성 응답(코드 포함) 제공.
  • 개선 사항

    • 생성 유효성 강화: 약관 필수, 이모지/제목/날짜/인원(1~64) 검증 및 주당 생성 제한(최대 3개).
    • 참가자 등록 흐름 개선 및 블랙리스트 검사 반영.
    • API 응답 포맷 및 OpenAPI 메타·서버 정보 갱신. 성공 코드 추가.
  • 테스트

    • 대규모 성능 벤치마크 및 테스트 픽스처 추가.
  • 기타

    • .gitignore 확장 및 빌드/테스트 설정·런타임 의존성 업데이트.

@zyovn zyovn self-assigned this Oct 20, 2025
@zyovn zyovn added the ✨feature New feature or request label Oct 20, 2025
@zyovn zyovn linked an issue Oct 20, 2025 that may be closed by this pull request
2 tasks
@coderabbitai
Copy link

coderabbitai bot commented Oct 20, 2025

Walkthrough

앨범 생성 API와 DTO/응답을 추가하고 도메인(ThemeEmoji, isTermsAgreement)과 참여자 모델을 UserAlbum→AlbumParticipant로 리팩터링했으며 검증·에러 코드·레포지토리·매퍼·컨트롤러·벤치마크·픽스처·빌드 설정을 함께 변경했습니다.

Changes

코호트 / 파일(s) 변경 요약
빌드·무시 설정
\.gitignore, build.gradle
application-test.yml 무시 규칙 추가; implementation 'com.github.f4b6a3:uuid-creator:5.2.0' 의존성 추가; 테스트 실행에서 useJUnitPlatform { excludeTags 'benchmark' } 설정 변경
도메인 리팩터링
src/main/java/com/cheeeese/album/domain/Album.java, src/main/java/com/cheeeese/album/domain/AlbumParticipant.java, .../domain/UserAlbumRole.java
AlbumthemeEmojiisTermsAgreement 필드 추가(컬럼 맵핑 포함); UserAlbumAlbumParticipant로 클래스명 및 roleisBlacklisted 변경; UserAlbumRole enum 삭제
영속성 계층
src/main/java/com/cheeeese/album/infrastructure/persistence/AlbumRepository.java, src/main/java/com/cheeeese/album/infrastructure/persistence/AlbumParticipantRepository.java, src/main/java/com/cheeeese/album/infrastructure/persistence/UserAlbumRepository.java
AlbumRepository에 주간 생성 카운트용 countByUserAndCreatedAtBetween 쿼리 추가; 신규 AlbumParticipantRepository 추가(유저·앨범 조회, 전체 참가자 조회, 블랙리스트 조회); 기존 UserAlbumRepository 삭제
서비스·검증·매퍼
src/main/java/com/cheeeese/album/application/AlbumService.java, src/main/java/com/cheeeese/album/application/validator/AlbumValidator.java, src/main/java/com/cheeeese/album/infrastructure/mapper/AlbumMapper.java
createAlbum 워크플로우 추가(시간순 UUID 생성, 주간 생성 제한 조회, 검증, 저장, 참가자 등록, 동시성 예외 매핑); AlbumValidatorvalidateAlbumCreation(createdThisWeek, request) 추가 및 블랙리스트 체크 변경; Mapper에 toEntity(...), toCreationResponse(...) 및 Guest→AlbumParticipant 변환 추가
컨트롤러·Swagger
src/main/java/com/cheeeese/album/presentation/AlbumController.java, src/main/java/com/cheeeese/album/presentation/swagger/AlbumSwagger.java
POST 앨범 생성 엔드포인트 createAlbum(User, AlbumCreationRequest) 추가 및 Swagger 명세 추가
DTOs / 에러·성공 코드
src/main/java/com/cheeeese/album/dto/request/AlbumCreationRequest.java, .../response/AlbumCreationResponse.java, .../exception/code/AlbumErrorCode.java, .../global/common/code/SuccessCode.java
앨범 생성 요청/응답 record 추가(themeEmoji, title, participant, eventDate, isTermsAgreement); 앨범 생성 관련 에러 코드 7종 추가; ALBUM_CREATE_SUCCESS 성공 코드 추가
설정 업데이트
src/main/java/com/cheeeese/global/config/SwaggerConfig.java
OpenAPI 버전 변경(v1 → v1.0.0) 및 서버 목록을 로컬/개발/프로덕션으로 확장
테스트·픽스처
src/test/java/com/cheeeese/album/benchmark/AlbumServiceBenchmarkTest.java, src/test/java/com/cheeeese/fixture/FixtureFactory.java
벤치마크 테스트 클래스 추가(20,000개 앨범 데이터, 선택/정렬 성능 측정); FixtureFactory 추가(v4/v7 UUID 기반 앨범 생성)

Sequence Diagram(s)

sequenceDiagram
    participant User as 사용자
    participant Controller as AlbumController
    participant Service as AlbumService
    participant Validator as AlbumValidator
    participant AlbumRepo as AlbumRepository
    participant Mapper as AlbumMapper
    participant ParticipantRepo as AlbumParticipantRepository

    User->>Controller: POST /albums (AlbumCreationRequest)
    Controller->>Service: createAlbum(user, request)

    rect rgb(235,245,255)
        Service->>AlbumRepo: countByUserAndCreatedAtBetween(userId, weekStart, weekEnd)
        AlbumRepo-->>Service: createdThisWeek
        Service->>Validator: validateAlbumCreation(createdThisWeek, request)
        Validator-->>Service: validation 성공/예외
    end

    rect rgb(235,255,235)
        Service->>Service: generate time-ordered UUID
        Service->>Mapper: toEntity(...)
        Mapper-->>Service: Album entity
        Service->>AlbumRepo: save(album)
        AlbumRepo-->>Service: saved Album
        Service->>ParticipantRepo: save(AlbumParticipant(hostId, albumId, isBlacklisted=false))
        ParticipantRepo-->>Service: saved Participant
    end

    rect rgb(255,250,230)
        Service->>Mapper: toCreationResponse(album)
        Mapper-->>Service: AlbumCreationResponse
        Service-->>Controller: AlbumCreationResponse
        Controller-->>User: 200 OK (CommonResponse)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • dahyun24

Poem

🐰 새 이모지로 꾸민 앨범 만들었네,
UUID 줄맞춰 시간에 새기고,
약관 체크하고 참가자 수 세며,
블랙리스트 살펴 천천히 저장—
픽스처와 벤치가 박수 보냈다 🎉

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed PR 제목 "feat: 앨범 생성 및 권한 관리 구현"은 변경사항의 주요 내용을 정확하게 반영하고 있습니다. 앨범 생성 기능과 관련하여 createAlbum 메서드, API 엔드포인트, 요청/응답 DTO가 새롭게 추가되었으며, 권한 관리와 관련하여 UserAlbum에서 AlbumParticipant로의 도메인 모델 전환이 이루어졌습니다. 제목은 간결하고 명확하며 오도하는 부분이 없고, 개발자가 이 PR의 핵심 변경사항을 쉽게 파악할 수 있습니다.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#14-album-create

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

Comment @coderabbitai help to get the list of available commands and usage tips.

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 (12)
src/main/java/com/cheeeese/album/infrastructure/persistence/AlbumRepository.java (1)

19-24: 파라미터명과 쿼리 필드 간 명명 불일치 개선 필요

메서드 파라미터는 userId로 명명되어 있지만, 실제 JPQL 쿼리에서는 a.hostId를 참조하고 있습니다. 이는 코드 가독성을 저해하고 혼란을 야기할 수 있습니다.

다음 중 하나를 선택하여 일관성을 개선하세요:

방안 1 (권장): 파라미터명을 hostId로 변경

-    @Query("SELECT COUNT(a) FROM Album a WHERE a.hostId = :userId AND a.createdAt BETWEEN :start AND :end")
+    @Query("SELECT COUNT(a) FROM Album a WHERE a.hostId = :hostId AND a.createdAt BETWEEN :start AND :end")
     long countByUserAndCreatedAtBetween(
-            @Param("userId") Long userId,
+            @Param("hostId") Long hostId,
             @Param("start") LocalDateTime start,
             @Param("end") LocalDateTime end
     );

방안 2: 메서드명을 hostId 기반으로 변경

-    @Query("SELECT COUNT(a) FROM Album a WHERE a.hostId = :userId AND a.createdAt BETWEEN :start AND :end")
-    long countByUserAndCreatedAtBetween(
-            @Param("userId") Long userId,
+    @Query("SELECT COUNT(a) FROM Album a WHERE a.hostId = :hostId AND a.createdAt BETWEEN :start AND :end")
+    long countByHostAndCreatedAtBetween(
+            @Param("hostId") Long hostId,
             @Param("start") LocalDateTime start,
             @Param("end") LocalDateTime end
     );
src/main/java/com/cheeeese/global/common/code/SuccessCode.java (1)

26-26: 성공 메시지 언어 일관성 개선 권장

기존 앨범 관련 성공 코드들(Line 24-25)은 한글 메시지를 사용하는 반면, 새로 추가된 ALBUM_CREATE_SUCCESS는 영문 메시지를 사용하고 있습니다. 일관성을 위해 한글 메시지 사용을 권장합니다.

-    ALBUM_CREATE_SUCCESS(HttpStatus.OK, "Album Create Success"),
+    ALBUM_CREATE_SUCCESS(HttpStatus.OK, "앨범 생성이 성공적으로 완료되었습니다."),
src/main/java/com/cheeeese/album/infrastructure/mapper/AlbumMapper.java (1)

21-47: toEntity 메서드 파라미터 개수 고려

현재 toEntity 메서드가 10개의 파라미터를 받고 있습니다. 파라미터가 많아 메서드 호출 시 실수 가능성이 있습니다.

더 나은 가독성을 위해 파라미터를 그룹화한 DTO나 빌더 패턴 사용을 고려하세요:

// 옵션 1: 생성 파라미터를 위한 DTO
public static Album toEntity(AlbumCreationParams params) {
    return Album.builder()
            .hostId(params.hostId())
            .title(params.title())
            // ...
            .build();
}

// 옵션 2: 현재 구조 유지 (현재도 충분히 명확함)

다만 현재 구조도 충분히 명확하므로 선택적으로 적용하세요.

src/main/java/com/cheeeese/album/presentation/swagger/AlbumSwagger.java (2)

34-43: 생성 성공 시 201 Created + Location 헤더로 표준화 권장

리소스 생성은 200보다 201이 적합합니다. Location 헤더(/albums/{code} 등)도 함께 문서화해주세요. 컨트롤러에는 @ResponseStatus(HttpStatus.CREATED)를, 스웨거에는 201 응답을 반영하면 좋습니다.

적용 예(스웨거 부분만 발췌):

- @ApiResponse(
-   responseCode = "200",
-   description = "앨범 생성이 성공적으로 실행되었습니다."
- )
+ @ApiResponse(
+   responseCode = "201",
+   description = "앨범 생성이 성공적으로 실행되었습니다. Location 헤더에 새 앨범 URL 포함"
+ )

22-33: RequestBody 상세는 DTO @Schema에 위임하여 중복 제거

AlbumCreationRequest에 필드별 @Schema가 이미 있으므로 여기 설명은 요약 위주로 두고 상세는 DTO로 위임하면 문서 드리프트를 줄일 수 있습니다. 또한 @Valid가 효과를 내도록 DTO에 Bean Validation을 추가(아래 DTO 코멘트 참고)하세요.

src/main/java/com/cheeeese/album/application/validator/AlbumValidator.java (1)

13-20: 테스트 가능성과 타임존 견고성: Clock 주입 권장

LocalDate.now() 대신 Clock을 주입해 사용하면(예: LocalDate.now(clock)) 테스트가 안정되고 서버 타임존/서머타임 이슈를 줄일 수 있습니다.

적용 예:

-import java.time.LocalDate;
+import java.time.Clock;
+import java.time.LocalDate;

 @RequiredArgsConstructor
 public class AlbumValidator {

     private final AlbumRepository albumRepository;
     private final AlbumParticipantRepository albumParticipantRepository;
+    private final Clock clock;

-    if (request.eventDate().isAfter(LocalDate.now())) {
+    if (request.eventDate().isAfter(LocalDate.now(clock))) {
         throw new AlbumException(AlbumErrorCode.ALBUM_EVENT_DATE_INVALID);
     }

Also applies to: 35-37

src/main/java/com/cheeeese/album/infrastructure/persistence/AlbumParticipantRepository.java (2)

9-15: 조회 패턴에 맞춘 인덱스/유니크 제약 추가 권장

다음 쿼리를 자주 쓰므로 인덱스와 유니크 제약을 권장합니다. 엔티티(@table(indexes=…)/@UniqueConstraint) 또는 마이그레이션으로 추가해주세요.

  • (album_id, user_id) UNIQUE — 중복 참가 방지
  • (album_id, user_id, is_blacklisted) INDEX — 블랙리스트 조회 가속
  • (album_id) INDEX — 앨범별 전체 참가자 조회 가속

예시(DDL):

  • Postgres

    • CREATE UNIQUE INDEX ux_album_participant ON album_participant(album_id, user_id);
    • CREATE INDEX ix_album_participant_blacklist ON album_participant(album_id, user_id, is_blacklisted);
  • MySQL

    • ALTER TABLE album_participant ADD UNIQUE KEY ux_album_participant (album_id, user_id);
    • ALTER TABLE album_participant ADD KEY ix_album_participant_blacklist (album_id, user_id, is_blacklisted);

10-14: 메서드 시그니처의 파라미터 순서 통일(가독성)

findByUserIdAndAlbumId(...)findByAlbumIdAndUserIdAndIsBlacklistedTrue(...)의 파라미터 순서가 다릅니다. 혼동 방지를 위해 통일을 권장합니다(예: 모두 albumId, userId 순서). 호환성 유지를 위해 신규 메서드 추가 후 점진 교체를 추천합니다.

src/test/java/com/cheeeese/album/benchmark/AlbumServiceBenchmarkTest.java (1)

30-53: 벤치마크는 CI에서 기본 제외되도록 보장 필요

데이터 4만 건 삽입은 CI를 지연시킬 수 있습니다. @Tag("benchmark")는 좋지만, 빌드 설정에서 태그 제외를 명시하세요.

Gradle 예:

test {
  useJUnitPlatform {
    excludeTags 'benchmark'
  }
}

Maven(Surefire) 예:

<configuration>
  <properties>
    <configurationParameters>junit.jupiter.tags=!benchmark</configurationParameters>
  </properties>
</configuration>
src/test/java/com/cheeeese/fixture/FixtureFactory.java (1)

14-56: 픽스처 구성 명확 — 벤치마크/단위테스트에 유용합니다

v4/v7 구분과 코드 생성이 명확합니다. 유지보수를 위해 공통 상수(이벤트일, 만료일 offset 등)를 상단에 모아두면 중복을 줄일 수 있습니다.

src/main/java/com/cheeeese/album/dto/request/AlbumCreationRequest.java (1)

8-25: Bean Validation 추가로 컨트롤러 단에서 즉시 400 응답 처리

현재 DTO에 제약이 없어 @Valid가 효과가 없습니다. 필수/범위를 DTO에 선언해 조기 검증하세요. 서비스 Validator는 비즈니스 규칙만 담당하도록 분리하면 깔끔합니다.

적용 예:

 package com.cheeeese.album.dto.request;

 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Builder;
 
 import java.time.LocalDate;
+import jakarta.validation.constraints.*;

 @Builder
 @Schema(description = "앨범 생성 API")
 public record AlbumCreationRequest(
         @Schema(description = "썸네일 이미지", example = "https://theme.jpg")
-        String themeImageUrl,
+        @NotBlank(message = "썸네일 이미지는 필수입니다.")
+        String themeImageUrl,
 
         @Schema(description = "앨범 이름", example = "졸업식")
-        String title,
+        @NotBlank(message = "앨범 이름은 필수입니다.")
+        @Size(max = 100, message = "앨범 이름은 100자를 넘을 수 없습니다.")
+        String title,
 
         @Schema(description = "참여자 수", example = "64")
-        int participant,
+        @Min(value = 1, message = "참여자 수는 1명 이상이어야 합니다.")
+        @Max(value = 64, message = "참여자 수는 64명을 초과할 수 없습니다.")
+        int participant,
 
         @Schema(description = "행사 날짜", example = "2025-02-01")
-        LocalDate eventDate,
+        @NotNull(message = "행사 날짜는 필수입니다.")
+        @PastOrPresent(message = "행사 날짜는 오늘 또는 과거여야 합니다.")
+        LocalDate eventDate,
 
         @Schema(description = "앨범 생성 필수 약관 동의", example = "true")
-        boolean isTermsAgreement
+        @AssertTrue(message = "필수 약관에 동의해야 합니다.")
+        boolean isTermsAgreement
 ) {
 }

Based on learnings

src/main/java/com/cheeeese/album/application/AlbumService.java (1)

166-176: 메서드 레퍼런스 사용 고려

현재 구현은 올바르게 작동하지만, 람다 표현식을 메서드 레퍼런스로 간소화할 수 있습니다.

     private List<AlbumParticipantResponse> getParticipantResponses(Long albumId) {
         List<Long> participantUserIds = albumParticipantRepository.findAllByAlbumId(albumId).stream()
-                .map(AlbumParticipant::getUserId)
+                .map(AlbumParticipant::getUserId)
                 .collect(Collectors.toList());
         
         List<User> participants = userRepository.findAllById(participantUserIds);
         
         return participants.stream()
                 .map(AlbumMapper::toParticipantResponse)
                 .collect(Collectors.toList());
     }

참고: Line 168은 이미 메서드 레퍼런스를 사용하고 있으므로 추가 변경은 불필요합니다. Line 174도 마찬가지로 이미 최적화되어 있습니다.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a5cb698 and f26899c.

📒 Files selected for processing (20)
  • .gitignore (1 hunks)
  • build.gradle (1 hunks)
  • src/main/java/com/cheeeese/album/application/AlbumService.java (4 hunks)
  • src/main/java/com/cheeeese/album/application/validator/AlbumValidator.java (2 hunks)
  • src/main/java/com/cheeeese/album/domain/Album.java (3 hunks)
  • src/main/java/com/cheeeese/album/domain/AlbumParticipant.java (2 hunks)
  • src/main/java/com/cheeeese/album/domain/UserAlbumRole.java (0 hunks)
  • src/main/java/com/cheeeese/album/dto/request/AlbumCreationRequest.java (1 hunks)
  • src/main/java/com/cheeeese/album/dto/response/AlbumCreationResponse.java (1 hunks)
  • src/main/java/com/cheeeese/album/exception/code/AlbumErrorCode.java (1 hunks)
  • src/main/java/com/cheeeese/album/infrastructure/mapper/AlbumMapper.java (2 hunks)
  • src/main/java/com/cheeeese/album/infrastructure/persistence/AlbumParticipantRepository.java (1 hunks)
  • src/main/java/com/cheeeese/album/infrastructure/persistence/AlbumRepository.java (2 hunks)
  • src/main/java/com/cheeeese/album/infrastructure/persistence/UserAlbumRepository.java (0 hunks)
  • src/main/java/com/cheeeese/album/presentation/AlbumController.java (2 hunks)
  • src/main/java/com/cheeeese/album/presentation/swagger/AlbumSwagger.java (2 hunks)
  • src/main/java/com/cheeeese/global/common/code/SuccessCode.java (1 hunks)
  • src/main/java/com/cheeeese/global/config/SwaggerConfig.java (1 hunks)
  • src/test/java/com/cheeeese/album/benchmark/AlbumServiceBenchmarkTest.java (1 hunks)
  • src/test/java/com/cheeeese/fixture/FixtureFactory.java (1 hunks)
💤 Files with no reviewable changes (2)
  • src/main/java/com/cheeeese/album/infrastructure/persistence/UserAlbumRepository.java
  • src/main/java/com/cheeeese/album/domain/UserAlbumRole.java
🧰 Additional context used
🧬 Code graph analysis (3)
src/test/java/com/cheeeese/album/benchmark/AlbumServiceBenchmarkTest.java (1)
src/test/java/com/cheeeese/fixture/FixtureFactory.java (1)
  • FixtureFactory (12-57)
src/test/java/com/cheeeese/fixture/FixtureFactory.java (1)
src/main/java/com/cheeeese/album/infrastructure/mapper/AlbumMapper.java (1)
  • AlbumMapper (16-131)
src/main/java/com/cheeeese/album/application/AlbumService.java (2)
src/main/java/com/cheeeese/album/infrastructure/mapper/AlbumMapper.java (1)
  • AlbumMapper (16-131)
src/main/java/com/cheeeese/photo/application/PhotoService.java (1)
  • Service (12-29)
🔇 Additional comments (16)
.gitignore (1)

12-12: 테스트 설정 파일 무시 규칙 추가 적절함.

application-test.yml.gitignore에 추가한 것은 환경별 구성 파일을 버전 관리에서 제외하는 표준 관행입니다. 기존 환경 설정 파일들과 일관성 있게 배치되어 있습니다.

build.gradle (2)

54-55: UUID v7 지원 확인됨, 버전 업그레이드 고려 권장.

uuid-creator 5.2.0은 UUID v7 구현을 포함하고 있으며, 알범 코드 생성에 적합합니다. 다만 최신 버전은 6.1.1이며, 현재 버전은 2022년 10월 출시본으로 약 3년이 경과했습니다. 최신 버전으로의 업그레이드 가능 여부와 6.x 대의 주요 변경사항을 검토할 것을 권장합니다.

버전 5.2.0 대신 최신 버전(6.1.1)으로 업그레이드할 수 있는지, 그리고 마이그레이션 시 주의사항이 있는지 확인해주세요.


59-61: 벤치마크 테스트 필터링 적절히 설정됨.

JUnit Platform의 excludeTags 설정으로 벤치마크 테스트를 정상 테스트 실행 시 제외하는 것은 좋은 관행입니다. 다만 벤치마크 테스트가 @Tag("benchmark") 애너테이션으로 적절히 표시되어 있는지 확인해주세요.

src/main/java/com/cheeeese/global/config/SwaggerConfig.java (1)

20-20: 시맨틱 버저닝 적용이 좋습니다.

API 버전을 "v1"에서 "v1.0.0"으로 변경하여 시맨틱 버저닝을 따르는 것은 좋은 개선입니다.

src/main/java/com/cheeeese/album/exception/code/AlbumErrorCode.java (1)

17-22: LGTM!

앨범 생성 흐름에 필요한 검증 에러 코드들이 명확하고 적절하게 정의되었습니다. HttpStatus 매핑과 한글 에러 메시지 모두 적절합니다.

src/main/java/com/cheeeese/album/presentation/AlbumController.java (1)

26-33: LGTM!

앨범 생성 엔드포인트가 적절하게 구현되었습니다:

  • @Valid를 통한 요청 검증
  • @CurrentUser를 통한 사용자 인증
  • 서비스 레이어로의 적절한 위임
src/main/java/com/cheeeese/album/domain/Album.java (2)

61-62: LGTM!

필수 약관 동의 상태를 추적하기 위한 isTermsAgreement 필드가 적절하게 추가되었습니다. 컬럼 매핑과 nullable 제약조건이 올바르게 설정되어 있습니다.


85-86: LGTM!

빌더 패턴에 isTermsAgreement 파라미터가 올바르게 추가되고 할당되었습니다.

Also applies to: 100-100

src/main/java/com/cheeeese/album/domain/AlbumParticipant.java (2)

14-14: 클래스명과 테이블명 불일치 확인 필요

클래스명이 AlbumParticipant로 변경되었지만 테이블명은 여전히 user_album으로 유지되고 있습니다(Line 12). 이는 다음을 확인해야 합니다:

  1. 기존 데이터베이스 스키마와의 호환성 유지를 위한 의도적 선택인지
  2. 향후 테이블명도 변경할 계획인지

의도된 설계인지 확인 부탁드립니다. 만약 데이터베이스 마이그레이션이 계획되어 있다면, 이를 추적할 수 있도록 TODO 주석을 추가하거나 별도 이슈를 생성하는 것을 권장합니다.


29-30: LGTM!

role enum을 isBlacklisted boolean으로 변경한 것은 적절한 개선입니다:

  • 블랙리스트 여부를 명확하게 표현
  • 단순한 boolean 타입으로 복잡도 감소
  • 현재 요구사항에 더 적합한 모델링

Also applies to: 33-33, 36-36

src/main/java/com/cheeeese/album/infrastructure/mapper/AlbumMapper.java (3)

40-40: maxPhotoCount 하드코딩 정책 확인 필요

maxPhotoCount가 2000으로 하드코딩되어 있습니다. PR 설명에서 작성자가 "전사적으로 업로드 가능 사진 수가 2000장으로 고정된 점을 고려할 때 maxPhotoCount 컬럼이 필요한지" 질문하셨습니다.

다음 중 하나를 선택하는 것이 좋습니다:

  1. 값이 정말 불변이라면: DB 컬럼을 제거하고 상수로 관리

    public static final int DEFAULT_MAX_PHOTO_COUNT = 2000;
  2. 향후 변경 가능성이 있다면: 현재 구조 유지하되 설정으로 외부화

    // application.yml
    album:
      max-photo-count: 2000
  3. 앨범별로 다를 수 있다면: 현재처럼 DB 컬럼 유지

비즈니스 요구사항 확인이 필요합니다. 앨범별로 사진 수 제한이 다를 가능성이 있는지, 아니면 정말 시스템 전체에서 고정 값인지 확인 부탁드립니다.


52-60: LGTM!

Album 엔티티를 AlbumCreationResponse로 변환하는 로직이 명확하고 적절합니다.


114-120: LGTM!

AlbumParticipant로의 전환이 올바르게 적용되었으며, isBlacklisted를 false로 기본 설정하는 것이 적절합니다.

src/main/java/com/cheeeese/album/application/validator/AlbumValidator.java (1)

35-37: 미래 날짜 금지 규칙 확인 필요

현재 eventDate.isAfter(LocalDate.now())면 오류를 던집니다. 행사 전 미리 앨범 생성이 불가한 비즈니스가 맞는지 확인 부탁드립니다(요구사항에 따라 @PastOrPresent가 아니라 @FutureOrPresent일 수도 있습니다).

src/main/java/com/cheeeese/album/application/AlbumService.java (2)

3-33: LGTM!

필요한 의존성 임포트가 적절하게 추가되었습니다. AlbumParticipant 도메인 모델로의 리팩터링과 앨범 생성 기능을 위한 임포트가 잘 구성되어 있습니다.


104-132: LGTM! 동시성 처리가 잘 구현되었습니다.

앨범 참가 로직의 동시성 제어가 매우 잘 구현되어 있습니다:

  • @Transactional 컨텍스트 내에서 실행되어 예외 발생 시 롤백이 보장됩니다
  • DataIntegrityViolationException 처리로 동시 참가 시도를 감지합니다
  • 원자적 증가 로직(incrementParticipantCountAtomically)으로 정원 초과를 방지합니다
  • 업데이트 후 앨범을 다시 조회하여 최신 상태를 반환합니다

Copy link
Contributor

@dahyun24 dahyun24 left a comment

Choose a reason for hiding this comment

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

maxPhotoCount 필드를 만든 것은 기획쪽에서 비즈니스 모델로 최대 제한 사진 개수를 조정할 수 있다고 들어서 만들었습니다~
uuid 버전에 따른 테스트 결과 차이가 엄청나네요!

수고하셨습니다~

@zyovn
Copy link
Member Author

zyovn commented Oct 20, 2025

maxPhotoCount 필드를 만든 것은 기획쪽에서 비즈니스 모델로 최대 제한 사진 개수를 조정할 수 있다고 들어서 만들었습니다~ uuid 버전에 따른 테스트 결과 차이가 엄청나네요!

수고하셨습니다~

아하 감사합니다!!!🧌

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

♻️ Duplicate comments (4)
src/main/java/com/cheeeese/album/application/AlbumService.java (3)

64-64: 만료 기간 상수화 권장

이전 리뷰에서 제안된 만료 기간 상수화가 아직 적용되지 않았습니다. 매직 넘버 7을 상수로 추출하면 유지보수성이 향상됩니다.

다음과 같이 상수를 정의하세요:

+    private static final int ALBUM_EXPIRATION_DAYS = 7;
+
     @Transactional
     public AlbumCreationResponse createAlbum(User user, AlbumCreationRequest request) {
         // ...
-        LocalDateTime.now().plusDays(7),
+        LocalDateTime.now().plusDays(ALBUM_EXPIRATION_DAYS),

50-50: UUID 버전 불일치 여전히 미수정 (v6 → v7)

이전 리뷰에서 지적된 UUID 버전 이슈가 아직 수정되지 않았습니다. 현재 코드는 getTimeOrdered()를 사용하여 UUID v6을 생성하지만, PR 설명과 벤치마크 테스트 결과에 따르면 UUID v7을 사용하기로 결정했습니다.

다음과 같이 수정하세요:

-        String code = UuidCreator.getTimeOrdered().toString();
+        String code = UuidCreator.getTimeOrderedEpoch().toString();

97-103: 주간 앨범 생성 횟수 계산 로직 오류 (Critical)

이 메서드에는 두 가지 심각한 문제가 있습니다:

  1. 101번 라인 - LocalTime.now() 사용 오류:

    • 종료 시점을 LocalTime.now()로 계산하면 메서드 호출 시각에 따라 주간 범위가 달라집니다
    • 예: 오전 9시에 호출하면 다음 주 월요일 09:00까지, 오후 3시에 호출하면 15:00까지
    • 의도한 범위는 "이번 주 월요일 00:00:00 ~ 다음 주 월요일 00:00:00 (미포함)"이므로 LocalTime.MIN을 사용해야 합니다
  2. BETWEEN 연산자 경계 문제 (이전 리뷰에서 미수정):

    • SQL BETWEEN은 양쪽 경계를 모두 포함합니다
    • 다음 주 월요일 정확히 00:00:00에 생성된 앨범이 현재 주에 포함될 수 있습니다
    • Repository 메서드를 >= start AND < end 조건으로 변경해야 합니다

다음과 같이 수정하세요:

1단계: 101번 라인 수정

         return albumRepository.countByUserAndCreatedAtBetween(
                 user.getId(),
                 LocalDate.now().with(DayOfWeek.MONDAY).atTime(LocalTime.MIN),
-                LocalDate.now().with(DayOfWeek.MONDAY).plusWeeks(1).atTime(LocalTime.now())
+                LocalDate.now().with(DayOfWeek.MONDAY).plusWeeks(1).atTime(LocalTime.MIN)
         );

2단계: AlbumRepository에 exclusive upper bound 메서드 추가

AlbumRepository에 다음 메서드를 추가하세요:

@Query("SELECT COUNT(a) FROM Album a WHERE a.hostId = :hostId AND a.createdAt >= :start AND a.createdAt < :end")
long countByHostIdAndCreatedAtBetweenExclusive(@Param("hostId") Long hostId, 
                                                 @Param("start") LocalDateTime start, 
                                                 @Param("end") LocalDateTime end);

그리고 이 메서드를 사용하도록 변경:

return albumRepository.countByHostIdAndCreatedAtBetweenExclusive(
        user.getId(),
        LocalDate.now().with(DayOfWeek.MONDAY).atStartOfDay(),
        LocalDate.now().with(DayOfWeek.MONDAY).plusWeeks(1).atStartOfDay()
);
src/test/java/com/cheeeese/album/benchmark/AlbumServiceBenchmarkTest.java (1)

83-92: 평균 계산이 누락되어 있습니다

100회 반복 조회의 총 시간을 밀리초로 변환했지만, 100으로 나눠 평균을 계산하지 않았습니다. 레이블을 "전체 조회 시간"으로 변경하셨지만, 벤치마크에서는 일반적으로 평균 시간을 표시하는 것이 더 의미 있습니다.

수정 예시:

-        System.out.printf("[%s] 전체 조회 시간: %.2f ms%n", label, total / 1000000.0);
+        double avgMs = total / 100.0 / 1_000_000.0; // 100회 평균을 ms로 변환
+        System.out.printf("[%s] 평균 조회 시간: %.2f ms%n", label, avgMs);
🧹 Nitpick comments (2)
src/test/java/com/cheeeese/fixture/FixtureFactory.java (1)

30-56: 선택 사항: v4/v7 메서드 간 코드 중복 제거 고려

createAlbumV4createAlbumV7 메서드가 UUID 생성 로직을 제외하고 동일합니다.

리팩터링 예시:

+    private static Album createAlbumWithCode(int i, String prefix, String code) {
+        return AlbumMapper.toEntity(
+                1L,
+                prefix + "-" + i,
+                code,
+                "테스트 이미지",
+                4,
+                LocalDate.of(2025, 1, 1),
+                true,
+                LocalDateTime.now().plusDays(7),
+                true
+        );
+    }
+
     public static Album createAlbumV4(int i) {
-        return AlbumMapper.toEntity(
-                1L,
-                "v4-" + i,
-                UUID.randomUUID().toString(),
-                "테스트 이미지",
-                4,
-                LocalDate.of(2025, 1, 1),
-                true,
-                LocalDateTime.now().plusDays(7),
-                true
-        );
+        return createAlbumWithCode(i, "v4", UUID.randomUUID().toString());
     }

     public static Album createAlbumV7(int i) {
-        return AlbumMapper.toEntity(
-                1L,
-                "v7-" + i,
-                UuidCreator.getTimeOrderedEpoch().toString(),
-                "테스트 이미지",
-                4,
-                LocalDate.of(2025, 1, 1),
-                true,
-                LocalDateTime.now().plusDays(7),
-                true
-        );
+        return createAlbumWithCode(i, "v7", UuidCreator.getTimeOrderedEpoch().toString());
     }
src/test/java/com/cheeeese/album/benchmark/AlbumServiceBenchmarkTest.java (1)

62-72: 선택 사항: 메모리 효율성 개선

전체 앨범(40,000개)을 메모리에 로드한 후 Java 스트림으로 필터링하는 것은 비효율적입니다.

리포지토리 메서드 추가를 고려하세요:

List<Album> findAllByTitleStartingWith(String prefix);

사용 예시:

-    List<Album> allAlbums = albumRepository.findAll();
-
-    List<String> v4Codes = allAlbums.stream()
-            .filter(a -> a.getTitle().startsWith("v4"))
-            .map(Album::getCode)
-            .toList();
-
-    List<String> v7Codes = allAlbums.stream()
-            .filter(a -> a.getTitle().startsWith("v7"))
-            .map(Album::getCode)
-            .toList();
+    List<String> v4Codes = albumRepository.findAllByTitleStartingWith("v4")
+            .stream()
+            .map(Album::getCode)
+            .toList();
+
+    List<String> v7Codes = albumRepository.findAllByTitleStartingWith("v7")
+            .stream()
+            .map(Album::getCode)
+            .toList();
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f26899c and 2881b47.

📒 Files selected for processing (6)
  • src/main/java/com/cheeeese/album/application/AlbumService.java (4 hunks)
  • src/main/java/com/cheeeese/album/application/validator/AlbumValidator.java (2 hunks)
  • src/main/java/com/cheeeese/album/exception/code/AlbumErrorCode.java (1 hunks)
  • src/main/java/com/cheeeese/album/infrastructure/persistence/AlbumRepository.java (2 hunks)
  • src/test/java/com/cheeeese/album/benchmark/AlbumServiceBenchmarkTest.java (1 hunks)
  • src/test/java/com/cheeeese/fixture/FixtureFactory.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/com/cheeeese/album/infrastructure/persistence/AlbumRepository.java
  • src/main/java/com/cheeeese/album/exception/code/AlbumErrorCode.java
🧰 Additional context used
🧬 Code graph analysis (3)
src/main/java/com/cheeeese/album/application/AlbumService.java (2)
src/main/java/com/cheeeese/album/infrastructure/mapper/AlbumMapper.java (1)
  • AlbumMapper (16-131)
src/main/java/com/cheeeese/photo/application/PhotoService.java (1)
  • Service (12-29)
src/test/java/com/cheeeese/album/benchmark/AlbumServiceBenchmarkTest.java (1)
src/test/java/com/cheeeese/fixture/FixtureFactory.java (1)
  • FixtureFactory (12-57)
src/test/java/com/cheeeese/fixture/FixtureFactory.java (1)
src/main/java/com/cheeeese/album/infrastructure/mapper/AlbumMapper.java (1)
  • AlbumMapper (16-131)
🔇 Additional comments (3)
src/main/java/com/cheeeese/album/application/validator/AlbumValidator.java (1)

22-50: 제목 검증 추가 확인 완료!

이전 리뷰 코멘트에서 지적된 제목(title) 검증이 정상적으로 추가되었습니다 (31-33번 라인). 모든 검증 로직이 명확하고 올바르게 구현되어 있습니다.

src/test/java/com/cheeeese/album/benchmark/AlbumServiceBenchmarkTest.java (2)

94-106: 정렬 벤치마크 수정 확인 - 잘 해결되었습니다!

이전 리뷰에서 지적된 정렬 벤치마크 문제가 잘 수정되었습니다. 이제 WHERE a.title LIKE :prefix 조건으로 각 라벨별 데이터만 필터링하여 정렬하므로, v4와 v7의 정렬 성능을 정확하게 비교할 수 있습니다.


59-81: 참고: 검증 없는 관찰형 벤치마크

테스트 메서드에 assertion이 없어 성능 저하 시 실패하지 않습니다. PR 설명에 따르면 이 벤치마크 결과가 UUID v7 채택 결정에 활용되었으므로 의도된 것으로 보입니다.

향후 성능 회귀를 자동으로 감지하려면 기준 임계값과 assertion 추가를 고려하세요.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/com/cheeeese/album/application/AlbumService.java (1)

128-133: 원자적 업데이트 후 엔티티 재조회 시 1차 캐시 오염 가능성

JPA 1차 캐시에 기존 Album가 남아있으면 findById가 갱신 전 스냅샷을 반환할 수 있습니다. 아래 중 하나로 일관성을 보장하세요.

옵션 A(권장): 업데이트 쿼리에 clearAutomatically=true 지정

@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("update Album a set a.currentParticipant = a.currentParticipant + 1 " +
       "where a.id = :albumId and a.currentParticipant < a.participant")
int incrementParticipantCountAtomically(@Param("albumId") Long albumId);

옵션 B: 서비스에서 refresh 사용

-        return albumRepository.findById(album.getId())
+        entityManager.refresh(album);
+        return albumRepository.findById(album.getId())
                 .orElseThrow( ... );

(옵션 B는 EntityManager 주입이 필요)

♻️ Duplicate comments (4)
src/main/java/com/cheeeese/album/application/validator/AlbumValidator.java (1)

31-33: 제목 검증 추가 확인 — 반영 감사합니다

이전 피드백대로 title null/blank 검증이 추가되었습니다. 이 부분은 그대로 가면 됩니다.

src/main/java/com/cheeeese/album/application/AlbumService.java (3)

64-64: 만료일 7일 매직 넘버 상수화

상수로 추출하면 유지보수성이 좋아집니다.

-                LocalDateTime.now().plusDays(7),
+                LocalDateTime.now().plusDays(ALBUM_EXPIRATION_DAYS),

클래스 상단에:

+    private static final int ALBUM_EXPIRATION_DAYS = 7;

50-50: UUID v7 의도 불일치 — v6 생성 메서드 사용 중

UuidCreator.getTimeOrdered()는 v6입니다. v7을 쓰려면 getTimeOrderedEpoch()로 교체하세요.

-        String code = UuidCreator.getTimeOrdered().toString();
+        String code = UuidCreator.getTimeOrderedEpoch().toString();

97-103: 주간 카운트 경계 버그(치명적) — 상한 시각 오류

검증 결과, 리뷰 댓글의 지적이 정확합니다.

AlbumRepository의 쿼리는 올바릅니다(a.createdAt >= :start AND a.createdAt < :end), 하지만 AlbumService의 countUserAlbumsCreatedThisWeek() 메서드(97-101줄)에서 상한 시각을 잘못 전달하고 있습니다:

  • 현재 코드: LocalDate.now().with(DayOfWeek.MONDAY).plusWeeks(1).atTime(LocalTime.now())

    • 다음 주 월요일의 현재 시각까지 포함
    • 예: 수요일 14:30 호출 시 → 다음 주 월요일 14:30까지 카운트
  • 정정 필요: LocalDate.now().with(DayOfWeek.MONDAY).plusWeeks(1).atStartOfDay()

    • 다음 주 월요일 00:00:00부터 제외
    • 정확한 주간 경계: [월요일 00:00:00, 다음 주 월요일 00:00:00)

권장 수정:

    private long countUserAlbumsCreatedThisWeek(User user) {
        LocalDate today = LocalDate.now();
        LocalDateTime start = today.with(DayOfWeek.MONDAY).atStartOfDay();
-       LocalDateTime endExclusive = today.with(DayOfWeek.MONDAY).plusWeeks(1).atTime(LocalTime.now());
+       LocalDateTime endExclusive = today.with(DayOfWeek.MONDAY).plusWeeks(1).atStartOfDay();
        return albumRepository.countByUserAndCreatedAtBetween(
                user.getId(), start, endExclusive);
    }
🧹 Nitpick comments (9)
src/main/java/com/cheeeese/album/application/validator/AlbumValidator.java (3)

39-41: 시간 의존성 주입으로 테스트 안정성 강화

LocalDate.now() 직접 호출 대신 Clock 주입을 권장합니다. 타임존/자정 경계에서 플래키해질 수 있습니다.

@@
-        if (request.eventDate().isAfter(LocalDate.now())) {
+        if (request.eventDate().isAfter(LocalDate.now(clock))) {
             throw new AlbumException(AlbumErrorCode.ALBUM_EVENT_DATE_INVALID);
         }

그리고 필드 주입:

 @RequiredArgsConstructor
 public class AlbumValidator {
@@
-    private final AlbumParticipantRepository albumParticipantRepository;
+    private final AlbumParticipantRepository albumParticipantRepository;
+    private final java.time.Clock clock;

43-49: 매직 넘버 상수화(정원 64, 주간 생성 3)

가독성과 일관성을 위해 상수로 추출해 주세요.

 @Component
 @RequiredArgsConstructor
 public class AlbumValidator {
+    private static final int MAX_PARTICIPANT = 64;
+    private static final int MAX_CREATE_PER_WEEK = 3;
@@
-        if (request.participant() < 1 || request.participant() > 64) {
+        if (request.participant() < 1 || request.participant() > MAX_PARTICIPANT) {
             throw new AlbumException(AlbumErrorCode.ALBUM_INVALID_CAPACITY);
         }
@@
-        if (createdThisWeek >= 3) {
+        if (createdThisWeek >= MAX_CREATE_PER_WEEK) {
             throw new AlbumException(AlbumErrorCode.ALBUM_CREATION_LIMIT_EXCEEDED);
         }

75-77: 불필요한 엔티티 로딩 없이 존재 여부만 확인

Optional 조회 후 ifPresent로 예외를 던지는 대신 existsBy… 쿼리로 변경하면 I/O를 줄일 수 있습니다.

-        albumParticipantRepository.findByAlbumIdAndUserIdAndIsBlacklistedTrue(album.getId(), user.getId())
-                .ifPresent(blacklisted -> {
-                    throw new AlbumException(AlbumErrorCode.USER_IS_BLACKLISTED);
-                });
+        boolean blacklisted = albumParticipantRepository
+                .existsByAlbumIdAndUserIdAndIsBlacklistedTrue(album.getId(), user.getId());
+        if (blacklisted) {
+            throw new AlbumException(AlbumErrorCode.USER_IS_BLACKLISTED);
+        }
src/main/java/com/cheeeese/album/domain/Album.java (2)

33-35: DB 제약과 Validator 일치시키기 — emoji 컬럼 NOT NULL 권장

Validator에서 이모지 미선택을 금지하므로 DB 스키마도 일치시키는 것이 안전합니다.

-    @Column(name = "emoji")
+    @Column(name = "emoji", nullable = false)
     private String emoji;

68-70: 현재 시각 의존성 분리 — 만료 판정 테스트 안정화

엔티티 내부에서 LocalDateTime.now()를 직접 호출하면 테스트/타임존 이슈가 생깁니다. now를 인자로 받는 메서드를 추가하고 기존 메서드는 이를 위임하도록 개선을 제안합니다.

-    public boolean isExpired() {
-        return this.expiredAt.isBefore(LocalDateTime.now()) || this.status == AlbumStatus.EXPIRED;
-    }
+    public boolean isExpired() {
+        return isExpiredAt(LocalDateTime.now());
+    }
+
+    public boolean isExpiredAt(LocalDateTime now) {
+        return this.expiredAt.isBefore(now) || this.status == AlbumStatus.EXPIRED;
+    }
src/main/java/com/cheeeese/album/dto/request/AlbumCreationRequest.java (1)

8-25: 입력단 Bean Validation 추가로 1차 방어선 강화

컨트롤러에서 @Valid로 빠르게 걸러내도록 제약 annotation을 추가하면 UX/성능 측면에서 이점이 큽니다.

+import jakarta.validation.constraints.*;
@@
 public record AlbumCreationRequest(
-        @Schema(description = "썸네일 이모지", example = "U+1F9C0")
-        String emoji,
+        @Schema(description = "썸네일 이모지", example = "U+1F9C0")
+        @NotBlank
+        String emoji,
@@
-        @Schema(description = "앨범 이름", example = "졸업식")
-        String title,
+        @Schema(description = "앨범 이름", example = "졸업식")
+        @NotBlank
+        String title,
@@
-        @Schema(description = "참여자 수", example = "64")
-        int participant,
+        @Schema(description = "참여자 수", example = "64")
+        @Min(1) @Max(64)
+        int participant,
@@
-        @Schema(description = "행사 날짜", example = "2025-02-01")
-        LocalDate eventDate,
+        @Schema(description = "행사 날짜", example = "2025-02-01")
+        @NotNull @PastOrPresent
+        LocalDate eventDate,
@@
-        @Schema(description = "앨범 생성 필수 약관 동의", example = "true")
-        boolean isTermsAgreement
+        @Schema(description = "앨범 생성 필수 약관 동의", example = "true")
+        @AssertTrue
+        boolean isTermsAgreement
 ) {
 }
src/main/java/com/cheeeese/album/exception/code/AlbumErrorCode.java (1)

17-23: HTTP 상태 코드 재검토(선택)

주간 생성 한도 초과는 409도 가능하지만, 빈도 제어 관점에서는 429(Too Many Requests)를 고려할 수 있습니다. 팀 API 규약에 맞춰 결정하면 좋겠습니다.

src/main/java/com/cheeeese/album/infrastructure/mapper/AlbumMapper.java (2)

40-40: maxPhotoCount를 파라미터로 받는 것을 고려해보세요.

현재 maxPhotoCount가 2000으로 하드코딩되어 있습니다. PR 설명에서 언급하신 것처럼 기획팀에서 앨범별로 최대 사진 개수를 조정할 가능성이 있다면, 이를 메서드 파라미터로 받아 유연하게 대응할 수 있도록 하는 것이 좋습니다.

다음과 같이 수정을 고려해보세요:

 public static Album toEntity(
         Long hostId,
         String title,
         String code,
         String emoji,
         int participant,
         LocalDate eventDate,
         boolean isInfoAvailable,
         LocalDateTime expiredAt,
-        boolean isTermsAgreement
+        boolean isTermsAgreement,
+        int maxPhotoCount
 ) {
     return Album.builder()
             .hostId(hostId)
             .title(title)
             .code(code)
             .emoji(emoji)
             .participant(participant)
             .currentParticipant(1)
             .eventDate(eventDate)
-            .maxPhotoCount(2000)
+            .maxPhotoCount(maxPhotoCount)
             .currentPhotoCount(0)
             .isInfoAvailable(isInfoAvailable)
             .expiredAt(expiredAt)
             .status(Album.AlbumStatus.ACTIVE)
             .isTermsAgreement(isTermsAgreement)
             .build();
 }

112-112: 주석을 명확히 수정해주세요.

"isBlacklisted = false 역할의"라는 표현이 부정확합니다. isBlacklisted는 역할(role)이 아니라 불리언 플래그입니다. 다음과 같이 수정하는 것이 좋겠습니다:

-     * User와 Album 엔티티를 기반으로 isBlacklisted = false 역할의 AlbumParticipant 엔티티를 생성합니다.
+     * User와 Album 엔티티를 기반으로 isBlacklisted = false로 설정된 AlbumParticipant 엔티티를 생성합니다.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2881b47 and 0760c57.

📒 Files selected for processing (7)
  • src/main/java/com/cheeeese/album/application/AlbumService.java (4 hunks)
  • src/main/java/com/cheeeese/album/application/validator/AlbumValidator.java (2 hunks)
  • src/main/java/com/cheeeese/album/domain/Album.java (4 hunks)
  • src/main/java/com/cheeeese/album/dto/request/AlbumCreationRequest.java (1 hunks)
  • src/main/java/com/cheeeese/album/dto/response/AlbumCreationResponse.java (1 hunks)
  • src/main/java/com/cheeeese/album/exception/code/AlbumErrorCode.java (1 hunks)
  • src/main/java/com/cheeeese/album/infrastructure/mapper/AlbumMapper.java (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/com/cheeeese/album/dto/response/AlbumCreationResponse.java
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/com/cheeeese/album/application/AlbumService.java (2)
src/main/java/com/cheeeese/album/infrastructure/mapper/AlbumMapper.java (1)
  • AlbumMapper (16-131)
src/main/java/com/cheeeese/photo/application/PhotoService.java (1)
  • Service (12-29)
🔇 Additional comments (4)
src/main/java/com/cheeeese/album/application/AlbumService.java (1)

106-118: 동시성 방어 검토 메모

이미 참여한 사용자 선체크 + unique(userId, albumId) 제약 + DataIntegrityViolationException 매핑 흐름은 합리적입니다. 트랜잭션 롤백 정책이 RuntimeException(AlbumException 포함)에 대해 활성화되어 있는지 한 번만 확인해 주세요.

src/main/java/com/cheeeese/album/infrastructure/mapper/AlbumMapper.java (3)

49-60: LGTM!

앨범 생성 응답 DTO 매핑 로직이 명확하고 적절합니다.


115-119: 역할 구분 로직이 의도적으로 설계되었습니다.

검증 결과, AlbumParticipant는 역할을 저장하는 필드가 없습니다. 대신 호스트/게스트 구분은 Album.hostIdAlbumParticipant.userId를 비교하여 도출됩니다:

  • 호스트: userId == album.hostId인 경우
  • 게스트: 그 외 AlbumParticipant 참가자

toGuestUserAlbum() 메서드 이름과 주석이 이 설계를 명확히 보여줍니다. 역할을 별도 필드로 저장하지 않고 관계로 도출하는 것은 정상적인 설계 패턴입니다.


38-38: currentParticipant 초기화 값이 호스트 중복 계산을 야기할 가능성 검증 필요

코드 분석 결과, 다음 흐름이 확인되었습니다:

  1. 앨범 생성 시: currentParticipant = 1로 초기화 (호스트 사전 계산)
  2. handleAlbumParticipation() 실행 시:
    • 호스트의 AlbumParticipant 레코드 저장 (line 117)
    • currentParticipant 원자적으로 증가 (line 119)

결과적으로 실제 AlbumParticipant 레코드는 1개(호스트)이지만, currentParticipant는 2로 계산되어 불일치가 발생할 수 있습니다. 이 로직이 의도된 설계(별도의 카운터 역할)인지, 아니면 호스트 중복 계산 버그인지 확인하여 데이터 일관성을 보장해주세요.

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

♻️ Duplicate comments (5)
src/main/java/com/cheeeese/album/dto/response/AlbumCreationResponse.java (1)

8-8: record에서 @builder 제거 권장

Java record는 이미 모든 필드를 받는 표준 생성자를 제공하므로, Lombok의 @Builder 어노테이션이 불필요합니다. 일부 Lombok 버전에서는 record와 함께 사용 시 올바르게 작동하지 않을 수 있습니다.

-@Builder
 @Schema(description = "앨범 생성 API")
 public record AlbumCreationResponse(
src/main/java/com/cheeeese/album/dto/request/AlbumCreationRequest.java (1)

8-8: record에서 @builder 제거 권장

AlbumCreationResponse와 동일하게, Java record는 표준 생성자를 제공하므로 @Builder가 불필요합니다.

-@Builder
 @Schema(description = "앨범 생성 API")
 public record AlbumCreationRequest(
src/main/java/com/cheeeese/album/application/AlbumService.java (3)

64-64: 만료 기간 상수화 권장

하드코딩된 만료 기간 7을 상수로 추출하면 유지보수성이 향상됩니다.

+private static final int ALBUM_EXPIRATION_DAYS = 7;

-LocalDateTime.now().plusDays(7),
+LocalDateTime.now().plusDays(ALBUM_EXPIRATION_DAYS),

50-50: UUID 버전 불일치 - v7 대신 v6 생성 중

PR 목표에서 UUID v7 채택을 명시했으나, UuidCreator.getTimeOrdered()는 UUID v6을 반환합니다. UUID v7을 생성하려면 getTimeOrderedEpoch()를 사용해야 합니다.

-String code = UuidCreator.getTimeOrdered().toString();
+String code = UuidCreator.getTimeOrderedEpoch().toString();

97-103: 주간 범위 계산 오류 - 동적 시간 사용으로 인한 부정확한 집계

Line 101의 atTime(LocalTime.now())는 현재 시각을 사용하여 종료 시점을 동적으로 설정하므로, 호출 시각에 따라 집계 범위가 달라지는 문제가 있습니다.

예를 들어:

  • 오전 10시 호출 시: 다음 월요일 10:00까지 집계
  • 오후 3시 호출 시: 다음 월요일 15:00까지 집계

의도한 범위는 [이번 주 월요일 00:00, 다음 주 월요일 00:00)의 반개방 구간이어야 합니다.

 private long countUserAlbumsCreatedThisWeek(User user) {
     return albumRepository.countByUserAndCreatedAtBetween(
             user.getId(),
             LocalDate.now().with(DayOfWeek.MONDAY).atTime(LocalTime.MIN),
-            LocalDate.now().with(DayOfWeek.MONDAY).plusWeeks(1).atTime(LocalTime.now())
+            LocalDate.now().with(DayOfWeek.MONDAY).plusWeeks(1).atStartOfDay()
     );
 }

추가로, SQL의 BETWEEN 연산자는 양쪽 경계를 포함하므로, 저장소 메서드를 >=<를 사용하는 방식으로 변경하는 것을 권장합니다.

🧹 Nitpick comments (1)
src/main/java/com/cheeeese/album/application/validator/AlbumValidator.java (1)

22-50: 검증 로직이 잘 구현되었으나 타임존 고려 필요

전반적인 검증 로직은 올바르게 구현되었습니다. 다만 Line 39의 LocalDate.now()는 서버의 시스템 타임존을 사용하므로, 사용자와 서버가 다른 타임존에 있을 경우 "미래 날짜" 검증이 의도와 다르게 동작할 수 있습니다.

선택적 개선 사항:

  1. 타임존 명시화:
-if (request.eventDate().isAfter(LocalDate.now())) {
+if (request.eventDate().isAfter(LocalDate.now(ZoneId.of("Asia/Seoul")))) {
  1. 매직 넘버 상수화:
+private static final int MAX_PARTICIPANT_COUNT = 64;
+private static final int WEEKLY_ALBUM_CREATION_LIMIT = 3;

-if (request.participant() < 1 || request.participant() > 64) {
+if (request.participant() < 1 || request.participant() > MAX_PARTICIPANT_COUNT) {
     throw new AlbumException(AlbumErrorCode.ALBUM_INVALID_CAPACITY);
 }

-if (createdThisWeek >= 3) {
+if (createdThisWeek >= WEEKLY_ALBUM_CREATION_LIMIT) {
     throw new AlbumException(AlbumErrorCode.ALBUM_CREATION_LIMIT_EXCEEDED);
 }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0760c57 and 3281c2d.

📒 Files selected for processing (7)
  • src/main/java/com/cheeeese/album/application/AlbumService.java (4 hunks)
  • src/main/java/com/cheeeese/album/application/validator/AlbumValidator.java (2 hunks)
  • src/main/java/com/cheeeese/album/domain/Album.java (4 hunks)
  • src/main/java/com/cheeeese/album/dto/request/AlbumCreationRequest.java (1 hunks)
  • src/main/java/com/cheeeese/album/dto/response/AlbumCreationResponse.java (1 hunks)
  • src/main/java/com/cheeeese/album/dto/response/AlbumInvitationResponse.java (1 hunks)
  • src/main/java/com/cheeeese/album/infrastructure/mapper/AlbumMapper.java (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/com/cheeeese/album/application/AlbumService.java (2)
src/main/java/com/cheeeese/album/infrastructure/mapper/AlbumMapper.java (1)
  • AlbumMapper (16-131)
src/main/java/com/cheeeese/photo/application/PhotoService.java (1)
  • Service (12-29)
🔇 Additional comments (5)
src/main/java/com/cheeeese/album/dto/response/AlbumInvitationResponse.java (1)

14-15: 필드명 변경이 올바르게 적용되었습니다.

themeImageUrl에서 themeEmoji로의 필드명 변경이 도메인 모델의 변경사항과 일치하며, Swagger 문서도 적절하게 업데이트되었습니다.

src/main/java/com/cheeeese/album/domain/Album.java (1)

33-34: 도메인 모델 변경이 올바르게 적용되었습니다.

themeImageUrl에서 themeEmoji로의 필드명 변경과 isTermsAgreement 필드 추가가 적절하게 구현되었으며, 컬럼 매핑도 정확합니다.

Also applies to: 61-62

src/main/java/com/cheeeese/album/application/AlbumService.java (1)

105-133: 참가자 등록 로직이 잘 구현되었습니다.

동시성 처리를 위한 원자적 증가 및 DataIntegrityViolationException 처리가 적절하게 구현되었으며, 중복 등록 방지 로직도 올바릅니다.

src/main/java/com/cheeeese/album/infrastructure/mapper/AlbumMapper.java (2)

21-47: 앨범 엔티티 생성 로직 확인

toEntity 메서드가 잘 구현되었습니다. Line 40의 maxPhotoCount = 2000 하드코딩은 PR 코멘트에서 언급된 대로, 향후 앨범별로 조정 가능성을 고려한 설계입니다.


52-60: 생성 응답 매핑 로직이 올바르게 구현되었습니다.

toCreationResponse 메서드가 앨범 정보를 적절하게 매핑하고 있습니다. (단, AlbumCreationResponse@Builder 어노테이션 제거는 별도 코멘트 참고)

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

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3281c2d and 08c241d.

📒 Files selected for processing (2)
  • src/main/java/com/cheeeese/album/dto/response/AlbumEnterResponse.java (1 hunks)
  • src/main/java/com/cheeeese/album/infrastructure/mapper/AlbumMapper.java (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/com/cheeeese/album/infrastructure/mapper/AlbumMapper.java

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

Labels

✨feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: 앨범 생성 및 권한 관리 구현

3 participants