Conversation
- S3 업로드 책임 Service 계층으로 이동 - 파일 업로드 optional로 변경 - ArticleSearchRequest DTO 도입으로 검색 조건 확장 - API URL 구조 개선 (/all, /all/search → 통합) - 카테고리 검색 기능 추가 - deprecated 엔드포인트 추가하여 하위 호환성 유지 - 페이지네이션 기본값 createdAt DESC로 통일
|
Caution Review failedThe pull request is closed. Note
|
| Cohort / File(s) | Summary |
|---|---|
Controller API 확장 src/main/java/com/mylog/domain/article/ArticleController.java |
getArticles, getMyArticles 메서드에 keyword, tag, categoryId 쿼리 파라미터 추가. 파일 파라미터를 선택사항으로 변경. 하위호환성 유지를 위해 3개의 Deprecated 엔드포인트 추가. |
Read 서비스 계층 src/main/java/com/mylog/domain/article/ArticleReader.java |
신규 서비스 클래스로 검색(search), 조회(getArticle, getArticles), 존재 여부 확인 메서드 제공. 내 기사/전체 기사 검색 분기 로직 포함. |
Write 서비스 계층 src/main/java/com/mylog/domain/article/ArticleService.java, src/main/java/com/mylog/domain/article/ArticleWriter.java |
신규 ArticleWriter 클래스 추가. ArticleService에 MultipartFile 기반 이미지 업로드, 검색 메서드(searchArticles, searchMyArticles) 추가. |
검색 요청 DTO src/main/java/com/mylog/domain/article/dto/request/ArticleSearchRequest.java |
keyword, tag, categoryId, memberId, pageable을 포함하는 신규 레코드 타입. 필드 존재 여부 확인 헬퍼 메서드 제공. |
Repository 확장 src/main/java/com/mylog/domain/article/repository/ArticleRepositoryCustom.java, src/main/java/com/mylog/domain/article/repository/impl/ArticleRepositoryImpl.java |
신규 ArticleRepositoryImpl 구현. 8개의 JPQL 기반 검색/조회 메서드 추가(태그, 카테고리, 키워드 필터링 지원). |
Article 엔티티 변경 src/main/java/com/mylog/domain/article/entity/Article.java |
aiSummary(TEXT), aiSummaryStatus(AnalyzeStatus) 필드 추가. update(), updateAiSummary(), markAiSummaryFailed() 메서드 추가. isOwnedBy() 메서드 삭제. |
ArticleTag 엔티티 변경 src/main/java/com/mylog/domain/article/entity/ArticleTag.java |
@Setter 제거. equals(), hashCode() 메서드 추가(article.id, tag.id 기반). |
레거시 클래스 제거 src/main/java/com/mylog/domain/article/reader/ArticleReader.java, src/main/java/com/mylog/domain/article/writer/ArticleTagWriter.java, src/main/java/com/mylog/domain/article/writer/ArticleWriter.java |
기존 reader/writer 패키지의 3개 클래스 삭제. 기능을 재구성된 새 클래스로 이관. |
주요 검토 사항
🔴 보안 및 비즈니스 로직
1. 소유권 검증 제거 이슈
Article.isOwnedBy(Long userId)메서드 삭제됨- ArticleWriter의
update(),delete()메서드에서 여전히 소유권을 검증하지만, 호출 측에서 유효한 memberId를 제공해야 함 - 권장: ArticleService 계층에서 명시적인 소유권 검증 로직 추가 또는 엔티티 메서드 복구
2. 파일 업로드 검증 부재
- ArticleController에서
@RequestPart(required = false, value = "file") MultipartFile file파라미터 추가 - ArticleService.createArticle(), updateArticle()에서 null 파일 처리 로직 불명확
- 권장: 파일 크기, 확장자, MIME 타입 검증 로직 추가 필요. S3 업로드 실패 시 예외 처리 강화
3. 선택적 쿼리 파라미터의 모호한 조건
getArticles(keyword, tag, categoryId): 파라미터가 없으면 모든 기사, 하나라도 있으면 검색- ArticleReader.search()에서
searchAll(),searchMine()분기 로직이 복잡함 - 권장: 문서화하고, 엣지 케이스 테스트 (예: 빈 문자열 vs null)
4. 파일 업로드 후 저장 실패 시 복구 불가
- ArticleService.createArticle()에서 S3에 파일 업로드 후 DB 저장 실패 시, S3에 고아(orphan) 파일 남을 수 있음
- 권장: 트랜잭션 롤백 시 S3 파일 삭제 로직 추가
🟡 성능
1. N+1 쿼리 위험
- ArticleRepositoryImpl의 각 메서드에서
SELECT a FROM Article a JOIN FETCH a.member JOIN FETCH a.category - ArticleResponse 생성 시 TagReader를 통해 추가 쿼리 발생 가능성
- 권장: 페이징 처리 시 JOIN FETCH 성능 영향 확인. BatchSize 설정 검토
2. 대용량 파일 처리 미지원
- MultipartFile 다중 업로드 미지원. 파일 크기 제한 없음
- 권장: application.yml에서
spring.servlet.multipart.max-file-size,max-request-size설정 확인
3. 검색 쿼리 성능
searchMineByTagName(): ArticleTag 테이블 서브쿼리 사용searchAllByTitle(): 제목 필드에 LIKE 조건 (인덱스 활용도 확인 필요)- 권장: 데이터베이스 실행 계획 검토. 태그 필터링 성능 테스트
🟡 에러 핸들링 및 Spring Boot 베스트프랙티스
1. null 반환의 모호성
ArticleReader.getArticleById():findById(...).orElse(null)사용- 호출 측에서 null 체크 필요. Optional 반환 권장
- 권장: Optional 반환으로 변경
2. ArticleSearchRequest의 필드 조합 검증 부재
- 모든 필드가 선택적이므로, 유효하지 않은 검색 요청 가능 (예: 모든 파라미터 null)
- 현재는 기본값(모든 기사) 반환하지만, 의도와 다를 수 있음
- 권장: 명시적 validation 또는 문서화 강화
3. 트랜잭션 경계 불명확
- ArticleService 메서드들이 transaction을 명시하지 않음 (
@Transactional어노테이션 누락) - ArticleWriter, ArticleReader는 각각
@Transactional보유 - 권장: ArticleService의 모든 public 메서드에 적절한
@Transactional명시
4. 소유권 검증 예외 처리
- ArticleWriter에서 ACCESS_DENIED 예외 발생하지만, ArticleService에서 처리하지 않음
- 호출 측 ArticleController에서 예외 처리 여부 불명확
- 권장: GlobalExceptionHandler에서 명시적으로 처리되는지 확인
5. AI 요약 필드의 동기화 이슈
- Article의 aiSummary, aiSummaryStatus 필드 추가되었으나, 초기 상태 설정 없음
- 비동기 AI 처리 중 동시성 문제 (RACE condition) 가능성
- 권장: 초기 상태를 PENDING으로 설정. 낙관적 잠금(Optimistic Locking) 고려
Sequence Diagram(s)
기사 검색 기능의 제어 흐름을 시각화합니다:
sequenceDiagram
participant Client
participant ArticleController
participant ArticleReader
participant ArticleRepository
participant Repository as ArticleRepositoryImpl
participant DB as Database
Client->>ArticleController: GET /articles?keyword=...&tag=...
ArticleController->>ArticleReader: search(ArticleSearchRequest)
alt hasKeyword or hasTag or hasCategory
ArticleReader->>Repository: searchAllByTitle() OR<br/>searchAllByTagName() OR<br/>findAllByCategory()
else All parameters null
ArticleReader->>Repository: findAllCustom(Pageable)
end
Repository->>DB: Execute JPQL query<br/>(JOIN FETCH member, category)
DB-->>Repository: Article rows + count
Repository->>Repository: Map to ArticleResponse
Repository-->>ArticleReader: Page<ArticleResponse>
ArticleReader-->>ArticleController: Page<ArticleResponse>
ArticleController-->>Client: 200 OK {data, pagination}
sequenceDiagram
participant Client
participant ArticleController
participant ArticleService
participant S3Service
participant ArticleWriter
participant ArticleRepository
participant DB as Database
Client->>ArticleController: POST /articles (multipart: file, request)
ArticleController->>ArticleService: createArticle(request, memberId, file)
alt file is not null
ArticleService->>S3Service: uploadFile(file)
S3Service-->>ArticleService: imageUrl
else file is null
ArticleService->>ArticleService: imageUrl = null
end
ArticleService->>ArticleWriter: create(request, memberId, imageUrl)
ArticleWriter->>ArticleRepository: save(Article)
ArticleRepository->>DB: INSERT article
DB-->>ArticleRepository: success
ArticleRepository-->>ArticleWriter: saved Article
ArticleWriter-->>ArticleService: success
ArticleService-->>ArticleController: SuccessResponse
ArticleController-->>Client: 201 Created
Estimated code review effort
🎯 4 (Complex) | ⏱️ ~60 minutes
근거:
- 다중 계층 구조 변경 (Controller → Service → Reader/Writer → Repository)
- 신규 JPQL 기반 Repository 구현 (8개 메서드, 195줄)
- 소유권 검증 로직 재배치 및 AI 요약 필드 동시성 이슈
- 파일 업로드, 검색 필터링, 트랜잭션 경계의 복합적 검토 필요
- 엔티티 구조 변경 (aiSummary/Status 추가, isOwnedBy 제거)
Possibly related PRs
- Recover: 머지 충돌 해결 및 불필요 코드 정리 #51: ArticleController.createArticle 시그니처 변경 및 ArticleService 호출 방식 개선과 공통 관심사. 인증 파라미터 처리 변경
- Refactor: 폴더 구조 및 코드 리팩토링 #47: ArticleController, ArticleReader/Writer/Service, Article 엔티티, ArticleRepositoryImpl 등 동일 계층 파일들을 수정. S3Service 타입 변경(S3Service → S3Provider) 포함
- Refactor: 설정 파일 리팩토링 #52: 기존 QueryDSL 기반 ArticleRepositoryImpl 제거와 신규 EntityManager/JPQL 기반 구현 추가. 동일 파일의 완전 재작성
Poem
📚 기사를 찾아 날개를 달고,
태그와 키워드로 수색 기술 갈고,
AI의 지혜 요약에 담고,
파일 업로드 준비 탄탄히 했네—
이제 검색도 스무스하게 착 🚀
✨ Finishing Touches
- 📝 Generate docstrings (stacked PR)
- 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
- Create PR with unit tests
- Post copyable unit tests in a comment
- Commit unit tests in branch
dev
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 @coderabbitai help to get the list of available commands and usage tips.
Summary by CodeRabbit
릴리스 노트
새로운 기능
개선 사항
호환성