Skip to content

feat(#3): 도서 CRUD API 구현 - #6

Merged
fnzl54 merged 4 commits into
mainfrom
feat/#3
Aug 7, 2026
Merged

feat(#3): 도서 CRUD API 구현#6
fnzl54 merged 4 commits into
mainfrom
feat/#3

Conversation

@fnzl54

@fnzl54 fnzl54 commented Aug 6, 2026

Copy link
Copy Markdown
Member

연관 이슈

작업 사항

  • feat(#3): 도서 등록 API 추가
    • ISBN이 있는 경우에만 활성 도서 중 중복을 검사
  • feat(#3): 도서 단건 조회 API 추가
  • feat(#3): 도서 수정 API 추가
  • feat(#3): 도서 삭제 API 추가

테스트

  • 로컬 서버 테스트 완료

주의 사항 및 참고사항

@fnzl54 fnzl54 self-assigned this Aug 6, 2026
fun execute(request: Request): Result<BookResponse, BookError> {
val isbn = Book.normalizeIsbn(request.isbn)
if (isbn != null && bookRepository.findByIsbnAndDeletedAtIsNull(isbn) != null) {
return BookError.DUPLICATE_ISBN.err()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[MEDIUM] ISBN 중복 검사가 check-then-act라 동시 요청 시 레이스 컨디션 발생

Problem: findByIsbnAndDeletedAtIsNull 조회와 save 사이에 DB 락이나 유니크 제약이 없다. 두 요청이 같은 ISBN으로 거의 동시에 들어오면 둘 다 중복 검사를 통과한 뒤 각자 커밋되어, DUPLICATE_ISBN이 막으려는 불변식이 깨진 채로 활성 도서 2건이 동일 ISBN을 가지게 된다.

Evidence: Book.kt#L24isbn 컬럼에 unique = true가 없고, 레포지토리 전체를 확인해도 마이그레이션/스키마 파일이 없어 DB 레벨 유니크 제약이 전혀 없다. 같은 패턴이 UpdateBookService.kt#L25-L31에도 있다.

Fix direction: 소프트 삭제 특성상 단순 컬럼 유니크 제약은 부적합하므로(삭제된 도서의 ISBN은 재사용 가능해야 함), deleted_at IS NULL 조건의 부분 유니크 인덱스를 DB에 추가하고, 저장 시 발생하는 제약 위반 예외를 잡아 DUPLICATE_ISBN으로 변환하는 방식을 권장한다.

try {
    val book = bookRepository.save(
        Book(title = request.title, author = request.author, isbn = isbn),
    )
    return BookResponse.from(book).ok()
} catch (e: DataIntegrityViolationException) {
    return BookError.DUPLICATE_ISBN.err()
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

수정 완료

  • 인덱스
    deleted_at IS NULL 조건의 부분 인덱스는 PostgreSQL 기능으로 MySQL에서 사용이 불가능하여
    함수 기반 유니크 인덱스를 통해 인덱스를 추가하여 활성 도서끼리만 ISBN 중복을 금지

    CREATE UNIQUE INDEX ux_books_active_isbn
        ON books ((IF(deleted_at IS NULL, isbn, NULL)));
  • 예외 처리
    예외를 전파하여 핸들러에서 ConstraintKind.UNIQUE 만 409로 반환하고 있습니다.

if (owner != null && owner.id != id) {
return BookError.DUPLICATE_ISBN.err()
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[MEDIUM] 동일한 ISBN 레이스 컨디션이 수정 API에도 존재

Problem: owner == null || owner.id == id 검사와 book.update(...) 사이에 락이나 DB 유니크 제약이 없다. 서로 다른 도서를 같은 새 ISBN으로 동시에 수정하면 둘 다 검사를 통과한 뒤 커밋되어 DUPLICATE_ISBN이 막으려는 불변식이 깨진다.

Evidence: 동일 패턴에 대한 근본 원인과 수정 방향은 CreateBookService.kt#L23 코멘트에 정리했다.

Fix direction: deleted_at IS NULL 부분 유니크 인덱스를 추가하고, book.update(...) 저장 시 발생하는 제약 위반 예외를 DUPLICATE_ISBN으로 변환하는 방식을 동일하게 적용한다.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

수정 완료 (상단 리뷰 참고)

@fnzl54
fnzl54 merged commit 1f2d3aa into main Aug 7, 2026
@fnzl54
fnzl54 deleted the feat/#3 branch August 7, 2026 01:38
@fnzl54 fnzl54 changed the title 도서 CRUD API 구현 feat(#3): 도서 CRUD API 구현 Aug 7, 2026
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