Skip to content

Refactor(article): API 스펙 개선 및 구조 리팩토링#56

Merged
gratisreise merged 1 commit intomainfrom
dev
Mar 8, 2026
Merged

Refactor(article): API 스펙 개선 및 구조 리팩토링#56
gratisreise merged 1 commit intomainfrom
dev

Conversation

@gratisreise
Copy link
Owner

@gratisreise gratisreise commented Mar 8, 2026

  • S3 업로드 책임 Service 계층으로 이동
  • 파일 업로드 optional로 변경
  • ArticleSearchRequest DTO 도입으로 검색 조건 확장
  • API URL 구조 개선 (/all, /all/search → 통합)
  • 카테고리 검색 기능 추가
  • deprecated 엔드포인트 추가하여 하위 호환성 유지
  • 페이지네이션 기본값 createdAt DESC로 통일

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 게시글 검색에 키워드, 태그, 카테고리 필터링 기능 추가
    • 게시글에 AI 요약 기능 추가
  • 개선 사항

    • 게시글 생성/수정 시 이미지 파일 업로드 처리 개선
    • 파일 업로드를 선택 사항으로 변경하여 유연성 증대
  • 호환성

    • 기존 API 엔드포인트 호환성 유지

- S3 업로드 책임 Service 계층으로 이동
- 파일 업로드 optional로 변경
- ArticleSearchRequest DTO 도입으로 검색 조건 확장
- API URL 구조 개선 (/all, /all/search → 통합)
- 카테고리 검색 기능 추가
- deprecated 엔드포인트 추가하여 하위 호환성 유지
- 페이지네이션 기본값 createdAt DESC로 통일
@gratisreise gratisreise merged commit 545bdd1 into main Mar 8, 2026
0 of 3 checks passed
@coderabbitai
Copy link

coderabbitai bot commented Mar 8, 2026

Caution

Review failed

The pull request is closed.

Note

.coderabbit.yaml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized key(s) in object: 'linters', 'tools', 'pre_merge_checks', 'path_filters', 'path_instructions'
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • 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
ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 19747dc9-78c4-4c5d-b19d-c12aa8b5ae14

📥 Commits

Reviewing files that changed from the base of the PR and between 6623fd3 and 804e978.

📒 Files selected for processing (12)
  • src/main/java/com/mylog/domain/article/ArticleController.java
  • src/main/java/com/mylog/domain/article/ArticleReader.java
  • src/main/java/com/mylog/domain/article/ArticleService.java
  • src/main/java/com/mylog/domain/article/ArticleWriter.java
  • src/main/java/com/mylog/domain/article/dto/request/ArticleSearchRequest.java
  • src/main/java/com/mylog/domain/article/entity/Article.java
  • src/main/java/com/mylog/domain/article/entity/ArticleTag.java
  • src/main/java/com/mylog/domain/article/reader/ArticleReader.java
  • src/main/java/com/mylog/domain/article/repository/ArticleRepositoryCustom.java
  • src/main/java/com/mylog/domain/article/repository/impl/ArticleRepositoryImpl.java
  • src/main/java/com/mylog/domain/article/writer/ArticleTagWriter.java
  • src/main/java/com/mylog/domain/article/writer/ArticleWriter.java

📝 Walkthrough

Walkthrough

ArticleController, ArticleService, ArticleReader를 통해 기사 검색 기능을 확장하고, 파일 기반 이미지 업로드를 도입합니다. ArticleWriter, ArticleRepositoryImpl을 신규 추가하여 CQRS 패턴을 구현하고, Article 엔티티에 AI 요약 필드를 추가합니다.

Changes

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}
Loading
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

근거:

  • 다중 계층 구조 변경 (Controller → Service → Reader/Writer → Repository)
  • 신규 JPQL 기반 Repository 구현 (8개 메서드, 195줄)
  • 소유권 검증 로직 재배치 및 AI 요약 필드 동시성 이슈
  • 파일 업로드, 검색 필터링, 트랜잭션 경계의 복합적 검토 필요
  • 엔티티 구조 변경 (aiSummary/Status 추가, isOwnedBy 제거)

Possibly related PRs


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.

❤️ Share

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

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant