Skip to content

[Feat] 책 상세 조회 결과 api 개발#38

Merged
hd0rable merged 68 commits intodevelopfrom
feat/#34-get-book-detail-search
Jul 2, 2025
Merged

[Feat] 책 상세 조회 결과 api 개발#38
hd0rable merged 68 commits intodevelopfrom
feat/#34-get-book-detail-search

Conversation

@hd0rable
Copy link
Member

@hd0rable hd0rable commented Jun 29, 2025

#️⃣ 연관된 이슈

closes #34

📝 작업 내용

  • 책 상세조회 api를 개발했습니다.
  • isbn에 대한 유효성 검증을 위해 @Size,@Pattern 예외처리 하기위하여 handleConstraintViolationException를 작성했습니다.
  • NaverBookXmlParser,NaverApiUtil에 책 상세 조회가 추가되면서 기존에 있던 책 조회와의 중복 코드가 많아 공통 메서드는 추출해서 사용하는 식으로 리팩토링 했습니다.
  • 책 검색 조회와 책 상세 검색 조회의 useCase의 공통점이많아 하나의 BookSearchUseCase로 작성했습니다.
  • 책 상세 조회 흐름은 다음과 같습니다.
    Controller -> BookSearchUseCase를 구현한 BookSearchService.searchDetailBooks -> 도메인별 Command/Query Port -> 각 포트를 구현한 Adapter -> Service에서 BookDetailSearchResult반환 -> Controller에서 Result를 Response dto로 변환후 반환
  • 관련 테스트 코드도 작성했습니다.
  • bookDetailSearchUrl 환경변수 처리하여 yml 노션에 업데이트했습니다.
  • book <-> category, user <-> alias JPA 연관관계 잘못 매핑되어있던거 수정했습니다.

📸 스크린샷

아래의 테스트 데이터를 활용해 정상적으로 데이터가 반환되는 것을 확인했습니다.

INSERT INTO books (book_id, title, isbn, author_name, best_seller, publisher, image_url, page_count, description, created_at, modified_at)
VALUES
    (1, '작별하지 않는다', '9788954682152', '한강', false, '문학동네', 'https://image1.jpg', 300, '한강의 소설', '2025-06-30 10:00:00', '2025-06-30 11:00:00'),
    (2, '데미안', '9791141602376', '헤르만 헤세', false, '민음사', 'https://image2.jpg', 250, '성장 소설', '2025-06-30 10:10:00', '2025-06-30 11:10:00');


INSERT INTO aliases (alias_id, alias_value, image_url, alias_color, created_at, modified_at)
VALUES
    (1, '책벌레', 'https://img.example.com/alias1.png', '#FF5733', '2025-06-30 09:00:00', '2025-06-30 09:10:00'),
    (2, '밤의독서가', 'https://img.example.com/alias2.png', '#33C1FF', '2025-06-30 09:20:00', '2025-06-30 09:30:00'),
    (3, '지식탐험가', 'https://img.example.com/alias3.png', '#9C33FF', '2025-06-30 09:40:00', '2025-06-30 09:50:00');

INSERT INTO users (user_id,oauth2_id, nickname, image_url, role, user_alias_id, created_at, modified_at)
VALUES
    (1, 'kakao_432708231', 'User1', 'https://avatar1.jpg', 'USER', 1, '2025-06-30 10:20:00', '2025-06-30 11:20:00'),
    (2, 'kakao_132708232', 'User2', 'https://avatar2.jpg', 'USER', 1, '2025-06-30 10:30:00', '2025-06-30 11:30:00'),
    (3, 'kakao_232708232', 'User3', 'https://avatar3.jpg', 'USER', 1, '2025-06-30 10:40:00', '2025-06-30 11:40:00');

INSERT INTO categories (category_id, category_value, category_alias_id,created_at, modified_at)
VALUES (1, '소설', 1,'2025-06-30 09:00:00', '2025-06-30 09:30:00');


INSERT INTO rooms (room_id, title, description, is_public, password, room_percentage, start_date, end_date, recruit_count, book_id, category_id, created_at, modified_at)
VALUES
    (1, '한강 독서모임', '한강 작품 읽기 모임', true, NULL, 0.0, '2025-07-01', '2025-08-01', 10, 1, 1, '2025-06-30 12:00:00', '2025-06-30 12:10:00'),
    (2, '데미안 독서모임', '데미안 읽기 모임', false, 1234, 0.0, '2025-07-05', '2025-08-05', 8, 2, 1, '2025-06-30 12:20:00', '2025-06-30 12:30:00');


INSERT INTO user_rooms (current_page, user_percentage, user_role, user_id, room_id, created_at, modified_at)
VALUES
    (10, 10.0, 'MEMBER', 1, 1, '2025-06-30 13:00:00', '2025-06-30 13:10:00'),
    (0, 0.0, 'MEMBER', 2, 1, '2025-06-30 13:20:00', '2025-06-30 13:30:00'),
    ( 5, 5.0, 'HOST', 3, 2, '2025-06-30 13:40:00', '2025-06-30 13:50:00'),
    (0, 0.0, 'MEMBER', 1, 2, '2025-06-30 14:00:00', '2025-06-30 14:10:00');


INSERT INTO posts (post_id, dtype, content, user_id, created_at, modified_at)
VALUES
    (1, 'FEED', '한강 책 너무 좋아요!', 1, '2025-06-30 15:00:00', '2025-06-30 15:10:00'),
    (2, 'FEED', '데미안에 대해 토론해요.', 2, '2025-06-30 15:20:00', '2025-06-30 15:30:00'),
    (3, 'FEED', '작별하지 않는다 감상문', 3, '2025-06-30 15:40:00', '2025-06-30 15:50:00');


INSERT INTO feeds (post_id, is_public, report_count, book_id)
VALUES
    (1, true, 0, 1),
    (2, true, 0, 2),
    (3, false, 0, 1);

INSERT INTO saved_books (user_id, book_id, created_at, modified_at)
VALUES
    (1, 1, '2025-06-30 17:00:00', '2025-06-30 17:10:00'),
    (2, 2, '2025-06-30 17:20:00', '2025-06-30 17:30:00'),
    (1, 2, '2025-06-30 17:40:00', '2025-06-30 17:50:00');

💬 리뷰 요구사항

querydsl 사용하니까 확실히 조회로직이 간단해지긴하는데 더 좋은 조회로직이있을까용? 검토해주시면감사하겠습니다!
중간에 이슈번호 오타나서 잘못 커밋했는데.. 무시해주세요 ㅎ

📌 PR 진행 시 이러한 점들을 참고해 주세요

* P1 : 꼭 반영해 주세요 (Request Changes) - 이슈가 발생하거나 취약점이 발견되는 케이스 등
* P2 : 반영을 적극적으로 고려해 주시면 좋을 것 같아요 (Comment)
* P3 : 이런 방법도 있을 것 같아요~ 등의 사소한 의견입니다 (Chore)

Summary by CodeRabbit

  • 신규 기능

    • ISBN을 통한 도서 상세 조회 API가 추가되어, ISBN으로 도서 상세 정보를 확인할 수 있습니다.
    • 도서 상세 조회 시 모집 중인 방 개수, 독서 참여자 수, 사용자의 저장 여부 등 다양한 정보를 함께 제공합니다.
  • 버그 수정

    • 검증 오류 발생 시 상세한 메시지와 함께 일관된 에러 응답을 반환합니다.
  • 개선 및 리팩터링

    • 도서, 피드, 방, 저장 기능 등 여러 도메인의 데이터 접근 및 명명 방식이 개선되어 일관성이 향상되었습니다.
    • 외부 API 예외 처리 및 파싱 로직이 강화되어 안정성이 높아졌습니다.
  • 테스트

    • 도서 상세 조회 기능에 대한 통합 테스트가 추가되어 다양한 케이스를 검증합니다.

hd0rable added 30 commits June 29, 2025 22:07
seongjunnoh
seongjunnoh previously approved these changes Jun 30, 2025
Copy link
Collaborator

@seongjunnoh seongjunnoh left a comment

Choose a reason for hiding this comment

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

고생하셨습니다! 사소한 리뷰하나 남겼는데 확인부탁드립니다!

Comment on lines +27 to +31
//책 상세검색 결과 조회
@GetMapping("/books/{isbn}")
public BaseResponse<GetBookDetailSearchResponse> getBookDetailSearch(@PathVariable("isbn")
@Pattern(regexp = "\\d{13}") final String isbn,
@UserId final Long userId) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

오 GET api의 쿼리 파라미터에 대한 validation을 하는 코드는 처음보는데 좋은것 같습니다!
클래스 레벨에서의 @validated 선언 후 bean validation 하는 것도 좋습니다!

Comment on lines +96 to +112
private int getRecruitingReadCount(Book book) {
// 해당책으로 피드에 글 작성
// 해당책에 대해 모임방 참여
// 둘 중 하나라도 부합될 경우 카운트, 중복 카운트 불가

// 이 책으로 만들어진 방에 참여한 사용자 ID 집합
Set<Long> roomParticipantUserIds = userQueryPort.findUserIdsParticipatedInRoomsByBookId(book.getId());
// 이 책으로 피드를 쓴 사용자 ID 집합
Set<Long> feedAuthorUserIds = feedQueryPort.findUserIdsByBookId(book.getId());

// 합집합(중복 제거)
Set<Long> combinedUsers = new HashSet<>();
combinedUsers.addAll(roomParticipantUserIds);
combinedUsers.addAll(feedAuthorUserIds);
return combinedUsers.size();

}
Copy link
Collaborator

Choose a reason for hiding this comment

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

LGTM

Copy link
Contributor

Choose a reason for hiding this comment

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

👍🏻

Comment on lines 29 to +37
public class BookSearchService implements BookSearchUseCase {

private final SearchBookQueryPort searchBookQueryPort;
private final BookApiQueryPort bookApiQueryPort;
private final RoomQueryPort roomQueryPort;
private final UserQueryPort userQueryPort;
private final FeedQueryPort feedQueryPort;
private final SavedQueryPort savedQueryPort;
private final BookCommandPort bookCommandPort;
private final UserCommandPort userCommandPort;
Copy link
Collaborator

Choose a reason for hiding this comment

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

이렇게 여러 도메인의 Port 에 대한 의존성이 많아지는 것이 싫다면, BookQueryPort 내부에 여러 도메인들(= 테이블들)을 querydsl 을 활용해 join 해서 큰 단위의 데이터들을 조회하는 식으로 구현하는 것도 하나의 방법이 될 수 있습니다!
-> api의 response에 해당하는 데이터 포맷을 service에서 맞추냐, 아니면 Port 하위의 영속성 adapter에서 맞추냐 의 취향 차이 + DB를 여러번 왔다갔다 vs 한번에 비싼 join 연산 의 차이? 인것 같습니다

지금 코드도 충분히 Good 입니다

@SpringBootTest
@AutoConfigureMockMvc(addFilters = false)
@ActiveProfiles("test")
class BookDetailSearchControllerTest {
Copy link
Collaborator

Choose a reason for hiding this comment

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

LGTM

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: 0

♻️ Duplicate comments (2)
src/main/java/konkuk/thip/common/exception/handler/GlobalExceptionHandler.java (2)

28-28: 이전 리뷰 제안이 훌륭하게 반영되었습니다

@RequiredArgsConstructor를 사용하여 의존성 주입을 깔끔하게 처리했습니다.


123-150: 전략 패턴 구현이 완벽합니다

이전 리뷰에서 제안된 전략 패턴이 정확히 구현되어 있으며, 폴백 처리도 적절합니다. 다만 첫 번째 위반만 처리하는 부분에 대한 고려가 필요할 수 있습니다.

// 만약 모든 위반을 처리해야 한다면 다음과 같이 개선할 수 있습니다:
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
List<String> errorMessages = violations.stream()
    .map(violation -> {
        // 전략 패턴 적용하여 각 위반에 대한 메시지 생성
    })
    .collect(Collectors.toList());

하지만 현재 구현도 대부분의 케이스에서 충분합니다.

🧹 Nitpick comments (1)
src/main/java/konkuk/thip/common/exception/validation/SizeViolationStrategy.java (1)

12-12: 정적 임포트 일관성 개선 필요

API_INVALID_SIZE만 정적 임포트하고 있는데, line 27에서는 ErrorCode.API_INVALID_SIZE로 사용하고 있어 일관성이 부족합니다.

다음과 같이 수정하여 일관성을 맞추세요:

-                ErrorCode.API_INVALID_SIZE,
+                API_INVALID_SIZE,
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9134ee7 and 0a10f90.

📒 Files selected for processing (8)
  • src/main/java/konkuk/thip/book/adapter/out/persistence/BookCommandPersistenceAdapter.java (1 hunks)
  • src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java (1 hunks)
  • src/main/java/konkuk/thip/book/application/service/BookSearchService.java (3 hunks)
  • src/main/java/konkuk/thip/common/exception/handler/GlobalExceptionHandler.java (3 hunks)
  • src/main/java/konkuk/thip/common/exception/validation/ConstraintViolationResult.java (1 hunks)
  • src/main/java/konkuk/thip/common/exception/validation/ConstraintViolationStrategy.java (1 hunks)
  • src/main/java/konkuk/thip/common/exception/validation/PatternViolationStrategy.java (1 hunks)
  • src/main/java/konkuk/thip/common/exception/validation/SizeViolationStrategy.java (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/main/java/konkuk/thip/common/exception/validation/ConstraintViolationResult.java
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java
  • src/main/java/konkuk/thip/book/adapter/out/persistence/BookCommandPersistenceAdapter.java
  • src/main/java/konkuk/thip/book/application/service/BookSearchService.java
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/main/java/konkuk/thip/common/exception/validation/SizeViolationStrategy.java (1)
src/main/java/konkuk/thip/common/exception/validation/PatternViolationStrategy.java (1)
  • Component (13-30)
src/main/java/konkuk/thip/common/exception/validation/PatternViolationStrategy.java (1)
src/main/java/konkuk/thip/common/exception/validation/SizeViolationStrategy.java (1)
  • Component (15-32)
🔇 Additional comments (3)
src/main/java/konkuk/thip/common/exception/validation/ConstraintViolationStrategy.java (1)

7-10: 전략 패턴 인터페이스 설계가 우수합니다

인터페이스가 단일 책임 원칙을 잘 따르고 있으며, supportshandle 메서드의 분리로 명확한 역할을 정의하고 있습니다. 이전 리뷰에서 제안된 전략 패턴이 깔끔하게 구현되었습니다.

src/main/java/konkuk/thip/common/exception/validation/SizeViolationStrategy.java (1)

15-31: 전략 구현이 잘 되어 있습니다

@Size 어노테이션 위반에 대한 처리 로직이 명확하고, 한국어 에러 메시지도 적절합니다. 전략 패턴의 구현이 올바르게 되어 있습니다.

src/main/java/konkuk/thip/common/exception/validation/PatternViolationStrategy.java (1)

13-30: 일관성 있는 전략 구현입니다

SizeViolationStrategy와 동일한 패턴으로 구현되어 있어 코드 일관성이 우수합니다. 정적 임포트도 올바르게 사용되었습니다.

@hd0rable hd0rable requested a review from seongjunnoh June 30, 2025 12:11
Copy link
Contributor

@buzz0331 buzz0331 left a comment

Choose a reason for hiding this comment

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

복잡한 로직 구현하느라 수고 많으셨습니다~ 리뷰 확인 부탁드릴게욥

Comment on lines +33 to 50
public String searchBook(String keyword, int start) {
return callNaverApi(keyword, start, false);
}

public String detailSearchBook(String isbn) {
return callNaverApi(isbn, null, true);
}

private String callNaverApi(String keyword, Integer start, boolean isDetail) {
String query = keywordToEncoding(keyword);
String url = buildSearchApiUrl(query, start);
String url = isDetail ? buildDetailSearchApiUrl(query) : buildSearchApiUrl(query, start);

Map<String, String> requestHeaders = new HashMap<>();
requestHeaders.put("X-Naver-Client-Id", clientId);
requestHeaders.put("X-Naver-Client-Secret", clientSecret);

return get(url,requestHeaders);
return get(url, requestHeaders);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

p3: 현재 callNaverApi의 호출부 쪽에서 3번째 boolean 변수의 의미가 와닿지 않아서 리팩토링이 필요한 부분이라고 생각하는데 추후에 전략 패턴을 도입해봐도 좋을 것 같아요.

Copy link
Member Author

Choose a reason for hiding this comment

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

음 책검색, 책 상세 검색에서 네이버 api를 사용하다보니 공통 메서드로 추출해서 3번째 boolean 변수는 상세조회 검색인지를 뜻하는 값입니다!

Copy link
Contributor

Choose a reason for hiding this comment

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

callNaverApi() 내부에서는 boolean 파라미터가 어떤 역할을 하는지 이해되지만, searchBook()이나 detailSearchBook()처럼 호출하는 입장에서는 세 번째 인자가 어떤 의미인지 조금 직관적이지 않을 수 있다는 생각이 들었습니다!

현재 구조가 전체적으로 책 검색 api와 책 상세 검색 api가 유사한 부분과 미세하게 다른 흐름으로 나뉘는 것 같아서 전략 패턴을 고려해보는 것도 좋을 것 같아 리뷰 남겨보았습니다 ㅎ

Comment on lines 90 to 105
private static Element getFirstChannel(String xml) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setExpandEntityReferences(false);
DocumentBuilder builder = factory.newDocumentBuilder();
InputSource is = new InputSource(new StringReader(xml));
Document doc = builder.parse(is);

NodeList channelNodes = doc.getElementsByTagName("channel");
if (channelNodes.getLength() > 0) {
return (Element) channelNodes.item(0);
}
return null;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

p3: 메서드가 좀 커서 다음과 같이 메서드로 좀 분리하는거 어떤가욤

private static Document parseXml(String xml) throws Exception {
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
    factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
    factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
    factory.setExpandEntityReferences(false);
    DocumentBuilder builder = factory.newDocumentBuilder();
    return builder.parse(new InputSource(new StringReader(xml)));
}

private static Element getFirstChannel(String xml) throws Exception {
    Document doc = parseXml(xml);
    NodeList channelNodes = doc.getElementsByTagName("channel");
    return (channelNodes.getLength() > 0) ? (Element) channelNodes.item(0) : null;
}

Copy link
Member Author

Choose a reason for hiding this comment

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

xml 보안관련 로직은 따로 함수로 추출해보도록 하겠습니당

Copy link
Contributor

Choose a reason for hiding this comment

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

굿뜨~

Comment on lines +96 to +112
private int getRecruitingReadCount(Book book) {
// 해당책으로 피드에 글 작성
// 해당책에 대해 모임방 참여
// 둘 중 하나라도 부합될 경우 카운트, 중복 카운트 불가

// 이 책으로 만들어진 방에 참여한 사용자 ID 집합
Set<Long> roomParticipantUserIds = userQueryPort.findUserIdsParticipatedInRoomsByBookId(book.getId());
// 이 책으로 피드를 쓴 사용자 ID 집합
Set<Long> feedAuthorUserIds = feedQueryPort.findUserIdsByBookId(book.getId());

// 합집합(중복 제거)
Set<Long> combinedUsers = new HashSet<>();
combinedUsers.addAll(roomParticipantUserIds);
combinedUsers.addAll(feedAuthorUserIds);
return combinedUsers.size();

}
Copy link
Contributor

Choose a reason for hiding this comment

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

👍🏻

@@ -41,7 +49,10 @@ public enum ErrorCode implements ResponseCode {
BOOK_NAVER_API_RESPONSE_ERROR(HttpStatus.BAD_REQUEST, 80005,"네이버 API 응답에 실패하였습니다."),
Copy link
Contributor

@buzz0331 buzz0331 Jun 30, 2025

Choose a reason for hiding this comment

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

요거는 HttpStatus.INTERNAL_SERVER_ERROR가 맞아보이네욤

Comment on lines +18 to +31
@Override
public Set<Long> findUserIdsByBookId(Long bookId) {
QUserRoomJpaEntity userRoom = QUserRoomJpaEntity.userRoomJpaEntity;
QRoomJpaEntity room = QRoomJpaEntity.roomJpaEntity;

return new HashSet<>(
jpaQueryFactory
.select(userRoom.userJpaEntity.userId)
.distinct()
.from(userRoom)
.join(userRoom.roomJpaEntity, room)
.where(room.bookJpaEntity.bookId.eq(bookId))
.fetch()
);
Copy link
Contributor

Choose a reason for hiding this comment

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

💯

@hd0rable hd0rable requested a review from buzz0331 June 30, 2025 17:46
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 (1)
src/main/java/konkuk/thip/book/adapter/out/api/NaverBookXmlParser.java (1)

51-88: parseBookDetail 메서드에서 null 반환 문제를 해결해주세요.

메서드 끝에서 null을 반환하는 부분이 여전히 남아있어 이전 리뷰에서 지적된 문제가 해결되지 않았습니다. 호출하는 코드에서 NullPointerException이 발생할 수 있습니다.

다음과 같이 수정하세요:

        } catch (Exception e) {
            throw new ExternalApiException(BOOK_NAVER_API_PARSING_ERROR);
        }
-        return null;
+        throw new ExternalApiException(BOOK_NAVER_API_PARSING_ERROR);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0a10f90 and f914ecc.

📒 Files selected for processing (2)
  • src/main/java/konkuk/thip/book/adapter/out/api/NaverBookXmlParser.java (2 hunks)
  • src/main/java/konkuk/thip/common/exception/ExternalApiException.java (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/main/java/konkuk/thip/book/adapter/out/api/NaverBookXmlParser.java (1)
src/main/java/konkuk/thip/common/exception/ExternalApiException.java (1)
  • ExternalApiException (5-17)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (3)
src/main/java/konkuk/thip/book/adapter/out/api/NaverBookXmlParser.java (3)

19-49: parseBookList 메서드 리팩토링이 잘 되었습니다.

메서드명이 더 명확해졌고, 헬퍼 메서드 추출과 향상된 for-each 루프 사용으로 코드 가독성이 개선되었습니다. ExternalApiException 사용도 적절합니다.


90-98: XML 보안 설정이 잘 적용되었습니다.

이전 리뷰에서 지적된 XXE 공격 방지를 위한 보안 설정이 모두 올바르게 적용되었습니다.


100-114: 헬퍼 메서드 추출로 코드 구조가 개선되었습니다.

이전 리뷰에서 제안된 메서드 분리가 잘 적용되어 코드의 가독성과 재사용성이 향상되었습니다.

Comment on lines +7 to +17
private final ErrorCode errorCode;

public ExternalApiException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}
public ExternalApiException(ErrorCode errorCode, Exception e) {
super(errorCode.getMessage(), e);
this.errorCode = errorCode;
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

ErrorCode에 대한 getter 메서드를 추가해주세요.

errorCode 필드가 private final로 선언되어 있지만 getter 메서드가 없어서 외부에서 에러 코드에 접근할 수 없습니다. 에러 처리나 로깅에서 에러 코드 정보가 필요할 수 있습니다.

다음과 같이 getter 메서드를 추가하세요:

    public ExternalApiException(ErrorCode errorCode, Exception e) {
        super(errorCode.getMessage(), e);
        this.errorCode = errorCode;
    }
+
+    public ErrorCode getErrorCode() {
+        return errorCode;
+    }
}
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/common/exception/ExternalApiException.java around
lines 7 to 17, the private final field errorCode lacks a getter method,
preventing external access to the error code. Add a public getter method named
getErrorCode() that returns the errorCode field to enable error handling and
logging to access this information.

buzz0331
buzz0331 previously approved these changes Jul 2, 2025
Copy link
Contributor

@buzz0331 buzz0331 left a comment

Choose a reason for hiding this comment

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

👍🏻

@sonarqubecloud
Copy link

sonarqubecloud bot commented Jul 2, 2025

Copy link
Contributor

@buzz0331 buzz0331 left a comment

Choose a reason for hiding this comment

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

💯

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[THIP2025-70] [feat] 책 상세조회 api

3 participants