Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
829d0ce
[feat] 방 생성 api use case 개발 (#47)
seongjunnoh Jul 6, 2025
fddfc67
[feat] Book, Room 도메인 엔티티 내부에 메서드 추가 (#47)
seongjunnoh Jul 6, 2025
7d1dfa4
[feat] BookApiQueryPort 에 메서드 시그니처 추가 (#47)
seongjunnoh Jul 6, 2025
1856c55
[feat] facade 패턴을 활용한 BookApiQueryPort의 구현체 정의 (#47)
seongjunnoh Jul 6, 2025
4aca1e7
[feat] 알라딘 open api와의 통신 구현 (#47)
seongjunnoh Jul 6, 2025
41fdad1
[feat] value 값으로 Category 엔티티 조회하는 기능 구현 (#47)
seongjunnoh Jul 6, 2025
8b779b0
[feat] Book pageCount 값을 update 하는 기능 개발 (#47)
seongjunnoh Jul 6, 2025
7acbb2c
[feat] error code 추가 및 수정 (#47)
seongjunnoh Jul 6, 2025
b8b9ce7
[feat] Room save 기능 개발 (#47)
seongjunnoh Jul 6, 2025
8235fa1
[feat] 방 생성 api controller 개발 (#47)
seongjunnoh Jul 6, 2025
538b12c
[test] 방 생성 api 통합 테스트 (#47)
seongjunnoh Jul 6, 2025
54139ee
[test] 방 생성 api controller 단위 테스트 (#47)
seongjunnoh Jul 6, 2025
e106de5
[test] Room 도메인 엔티티 단위 테스트 (#47)
seongjunnoh Jul 6, 2025
aad2e93
develop merge (#47)
seongjunnoh Jul 8, 2025
ca99132
[refactor] Naver api 통신 객체 네이밍 변경 (#47)
seongjunnoh Jul 8, 2025
3349ae6
[refactor] AladinApiClient 메서드 네이밍 오타 수정 (#47)
seongjunnoh Jul 8, 2025
77852a8
[refactor] AladinApiUtil 에서 사용하는 파라미터 enum 으로 분리 (#47)
seongjunnoh Jul 8, 2025
c248b15
[refactor] todo 추가 (#47)
seongjunnoh Jul 8, 2025
fb3b5c2
[feat] Room 도메인 엔티티 생성 시 validation 추가 및 테스트 코드 작성 (#47)
seongjunnoh Jul 8, 2025
bbacd4e
[refactor] Book 도메인 엔티티 내부에 withId 메서드 삭제 및 상태 변경 메서드를 void로 수정 (#47)
seongjunnoh Jul 8, 2025
11dffd2
[fix] test yml을 활용해 테스트 실행하도록 명시 (#47)
seongjunnoh Jul 8, 2025
b56f48c
[feat] Room 생성 시 비밀번호 해싱 기능 추가 및 비밀번호 매칭 기능 추가 (#47)
seongjunnoh Jul 8, 2025
acd5976
[refactor] RoomTest 테스트 코드 수정 (#47)
seongjunnoh Jul 8, 2025
0ea0179
[refactor] 수정된 요구사항에 따라 Room 생성 조건 변경 (#47)
seongjunnoh Jul 8, 2025
9539c14
[refactor] 모임방 생성 시 인원 제한 요구사항 반영하도록 코드 수정 (#47)
seongjunnoh Jul 8, 2025
e13e5e6
[refactor] todo를 위한 userId Service 메서드의 인자로 추가 (#47)
seongjunnoh Jul 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package konkuk.thip.book.adapter.out.api;

import konkuk.thip.book.adapter.out.api.aladin.AladinApiClient;
import konkuk.thip.book.adapter.out.api.dto.NaverBookParseResult;
import konkuk.thip.book.adapter.out.api.dto.NaverDetailBookParseResult;
import konkuk.thip.book.application.port.out.BookApiQueryPort;
import konkuk.thip.book.domain.Book;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class CompositeBookApiAdapter implements BookApiQueryPort {

private final NaverApiClient naverApiClient;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

👍🏻

private final AladinApiClient aladinApiClient;

@Override
public NaverBookParseResult findBooksByKeyword(String keyword, int start) {
return naverApiClient.findBooksByKeyword(keyword, start);
}

@Override
public NaverDetailBookParseResult findDetailBookByIsbn(String isbn) {
return naverApiClient.findDetailBookByIsbn(isbn);
}

@Override
public Integer findPageCountByIsbn(String isbn) {
return aladinApiClient.findPageCountByIsbn(isbn);
}

@Override
public Book loadBookWithPageByIsbn(String isbn) {
// 1. naver 상세정보 조회 api 로 책 상세정보(without page) load
NaverDetailBookParseResult detailBookByKeyword = findDetailBookByIsbn(isbn);

// 2. 알라딘으로부터 책 page 정보 load
Integer pageCount = findPageCountByIsbn(isbn);

// 3. pageCount 정보를 포함한 Book 반환
return Book.withoutId(
detailBookByKeyword.title(),
isbn,
detailBookByKeyword.author(),
false, // TODO : 추후 BestSeller 도입되면 고려해야함
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

detailBookByKeyword.publisher(),
detailBookByKeyword.imageUrl(),
pageCount,
detailBookByKeyword.description()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,20 @@

import konkuk.thip.book.adapter.out.api.dto.NaverBookParseResult;
import konkuk.thip.book.adapter.out.api.dto.NaverDetailBookParseResult;
import konkuk.thip.book.application.port.out.BookApiQueryPort;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class BookApiNaverApiAdapter implements BookApiQueryPort {
public class NaverApiClient {

private final NaverApiUtil naverApiUtil;

@Override
public NaverBookParseResult findBooksByKeyword(String keyword, int start) {
String xml = naverApiUtil.searchBook(keyword, start); // 네이버 API 호출
return NaverBookXmlParser.parseBookList(xml); // XML 파싱 + 페이징 정보 포함
}

@Override
public NaverDetailBookParseResult findDetailBookByIsbn(String isbn) {
String xml = naverApiUtil.detailSearchBook(isbn); // 네이버 API 호출
return NaverBookXmlParser.parseBookDetail(xml); // XML 파싱
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import java.util.List;
import org.xml.sax.InputSource;

import static konkuk.thip.common.exception.code.ErrorCode.BOOK_ISBN_NOT_FOUND;
import static konkuk.thip.common.exception.code.ErrorCode.BOOK_NAVER_API_ISBN_NOT_FOUND;
import static konkuk.thip.common.exception.code.ErrorCode.BOOK_NAVER_API_PARSING_ERROR;

public class NaverBookXmlParser {
Expand Down Expand Up @@ -57,7 +57,7 @@ public static NaverDetailBookParseResult parseBookDetail(String xml) {
if (totalStr != null) total = Integer.parseInt(totalStr);

// total이 0이면 isbn에 해당하는 책이 없음(잘못 넘어온 isbn 예외처리)
if (total == 0) throw new BusinessException(BOOK_ISBN_NOT_FOUND);
if (total == 0) throw new BusinessException(BOOK_NAVER_API_ISBN_NOT_FOUND);

List<Element> items = getItemElements(channel);
if (!items.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package konkuk.thip.book.adapter.out.api.aladin;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class AladinApiClient {

private final AladinApiUtil aladinApiUtil;

public Integer findPageCountByIsbn(String isbn) {
return aladinApiUtil.getPageCount(isbn);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package konkuk.thip.book.adapter.out.api.aladin;

public enum AladinApiParam {

ITEM_ID_TYPE("ISBN"),
OUTPUT("js"),
API_VERSION("20131101"),
SUB_INFO_PARSING_KEY("subInfo"),
PAGE_COUNT_PARSING_KEY("itemPage");

Comment on lines +3 to +10
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

👍🏻

private final String value;

AladinApiParam(String value) {
this.value = value;
}

public String getValue() {
return value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package konkuk.thip.book.adapter.out.api.aladin;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import konkuk.thip.common.exception.BusinessException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;

import static konkuk.thip.book.adapter.out.api.aladin.AladinApiParam.*;
import static konkuk.thip.common.exception.code.ErrorCode.BOOK_ALADIN_API_ISBN_NOT_FOUND;
import static konkuk.thip.common.exception.code.ErrorCode.BOOK_ALADIN_API_PARSING_ERROR;

@Component
@RequiredArgsConstructor
@Slf4j
public class AladinApiUtil {

private final RestTemplate restTemplate;
private final ObjectMapper objectMapper;

@Value("${aladin.ttbKey}")
private String ttbKey;

@Value("${aladin.baseUrl}")
private String baseUrl;
Comment on lines +29 to +30
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

👍🏻



private String buildLookupUrl(String isbn) {
return String.format(
baseUrl + "ttbkey=%s&itemIdType=%s&itemId=%s&output=%s&Version=%s",
ttbKey,
ITEM_ID_TYPE.getValue(),
isbn,
OUTPUT.getValue(),
API_VERSION.getValue()
);
}

public Integer getPageCount(String isbn) {
String url = buildLookupUrl(isbn);
String response = restTemplate.getForObject(url, String.class);
Comment on lines +44 to +46
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

RestTemplate 타임아웃 설정 추가 권장

외부 API 호출 시 타임아웃이 설정되지 않아 응답이 없을 경우 무한 대기할 수 있습니다.

RestTemplateConfig에서 타임아웃을 설정하거나, 호출 시 타임아웃을 명시적으로 설정하는 것을 권장합니다.

// RestTemplateConfig.java에 추가
@Bean
public RestTemplate restTemplate() {
    RestTemplate restTemplate = new RestTemplate();
    restTemplate.setRequestFactory(requestFactory());
    return restTemplate;
}

@Bean
public ClientHttpRequestFactory requestFactory() {
    HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
    factory.setConnectTimeout(5000);
    factory.setReadTimeout(5000);
    return factory;
}
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/book/adapter/out/api/aladin/AladinApiUtil.java
around lines 42 to 44, the RestTemplate used for external API calls does not
have timeout settings, which can cause indefinite waiting if the API does not
respond. To fix this, configure a RestTemplate bean with connection and read
timeouts set (e.g., 5000 ms) in a RestTemplateConfig class, and inject this
configured RestTemplate into AladinApiUtil instead of using a default instance.


try {
JsonNode jsonNode = objectMapper.readTree(response);
JsonNode items = jsonNode.path("item");

// json 응답 결과에 item 키값이 없는 경우
if (!items.isArray() || items.isEmpty()) {
// TODO : 알라딘으로부터 page 정보가 없으면 ??
Comment thread
seongjunnoh marked this conversation as resolved.
// 보상 시나리오 : 유저에게 "page 정보를 찾을 수 없는 책입니다. 직접 page 정보를 입력하세요" 라고 안내
// 일단 지금은 exception throw 만 진행
throw new BusinessException(BOOK_ALADIN_API_ISBN_NOT_FOUND);
}

JsonNode subInfo = items.get(0).path(SUB_INFO_PARSING_KEY.getValue());

return subInfo.path(PAGE_COUNT_PARSING_KEY.getValue()).asInt();
} catch (IOException e) {
throw new BusinessException(BOOK_ALADIN_API_PARSING_ERROR);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,7 @@ public class BookJpaEntity extends BaseJpaEntity {
@Column(length = 1000)
private String description;

}
public void changePageCount(Integer pageCount) {
this.pageCount = pageCount;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,14 @@ public Book findById(Long id) {

return bookMapper.toDomainEntity(bookJpaEntity);
}

@Override
public void updateForPageCount(Book book) {
BookJpaEntity bookJpaEntity = bookJpaRepository.findById(book.getId()).orElseThrow(
() -> new EntityNotFoundException(BOOK_NOT_FOUND)
);

bookJpaEntity.changePageCount(book.getPageCount());
bookJpaRepository.save(bookJpaEntity);
}
Comment on lines +43 to +51
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Jul 6, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

트랜잭션 처리 및 메서드명 개선 필요

  1. 메서드에 @Transactional 어노테이션이 누락되어 있어 데이터 일관성 문제가 발생할 수 있습니다.
  2. 메서드명 updateForPageCount가 비즈니스 의도를 명확히 표현하지 못합니다.

다음과 같이 개선하는 것을 권장합니다:

+@Transactional
 @Override
-public void updateForPageCount(Book book) {
+public void updateBookPageCount(Book book) {
     BookJpaEntity bookJpaEntity = bookJpaRepository.findById(book.getId()).orElseThrow(
             () -> new EntityNotFoundException(BOOK_NOT_FOUND)
     );

     bookJpaEntity.changePageCount(book.getPageCount());
     bookJpaRepository.save(bookJpaEntity);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Override
public void updateForPageCount(Book book) {
BookJpaEntity bookJpaEntity = bookJpaRepository.findById(book.getId()).orElseThrow(
() -> new EntityNotFoundException(BOOK_NOT_FOUND)
);
bookJpaEntity.changePageCount(book.getPageCount());
bookJpaRepository.save(bookJpaEntity);
}
@Transactional
@Override
public void updateBookPageCount(Book book) {
BookJpaEntity bookJpaEntity = bookJpaRepository.findById(book.getId()).orElseThrow(
() -> new EntityNotFoundException(BOOK_NOT_FOUND)
);
bookJpaEntity.changePageCount(book.getPageCount());
bookJpaRepository.save(bookJpaEntity);
}
🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/book/adapter/out/persistence/BookCommandPersistenceAdapter.java
around lines 43 to 51, add the @Transactional annotation to the
updateForPageCount method to ensure data consistency during the update
operation. Also, rename the method to a more descriptive name like
updatePageCount to clearly express its business intent.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

코래 말대로 @Transactional 필요해보이네요!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@buzz0331 맞습니다!

@Transactional 어노테이션이 중요한 이유는 이 메서드가 데이터베이스에서 엔티티를 조회하고 수정한 후 저장하는 두 단계의 작업을 수행하기 때문입니다. 트랜잭션 없이는 동시성 문제나 부분 업데이트 상황에서 데이터 일관성이 보장되지 않을 수 있어요.

특히 페이지 수 업데이트와 같은 중요한 책 정보 변경 작업은 원자성이 보장되어야 하므로 @Transactional을 추가하는 것이 좋겠습니다! 🎯


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

서비스에서 @ Transactional 처리되고있지 않나요??

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

아 머야 adapter구나 예리하시다

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

넵 하나의 api 에 해당하는 로직의 원자성을 지키는 것이 좋지않나 싶어서 api 를 처리하는 Service 클래스의 public 메서드에 @transactional 어노테이션을 추가해두었습니다!

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,15 @@

import konkuk.thip.book.adapter.out.api.dto.NaverBookParseResult;
import konkuk.thip.book.adapter.out.api.dto.NaverDetailBookParseResult;
import konkuk.thip.book.domain.Book;

public interface BookApiQueryPort {

NaverBookParseResult findBooksByKeyword(String keyword, int start);

NaverDetailBookParseResult findDetailBookByIsbn(String isbn);

Integer findPageCountByIsbn(String isbn);

Book loadBookWithPageByIsbn(String isbn);
Comment on lines +13 to +15
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Jul 6, 2025

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

CQRS 포트 분리 컨벤션 검토 필요

loadBookWithPageByIsbn 메서드가 Book 도메인 엔티티를 반환하는데, 학습된 컨벤션에 따르면 QueryPort는 조회 API response 데이터 조회용이고, CommandPort는 도메인 엔티티 조회용입니다. 현재 구현이 프로젝트의 CQRS 분리 컨벤션과 일치하는지 확인이 필요합니다.

다음 스크립트로 기존 포트 인터페이스의 패턴을 확인해보겠습니다:


🏁 Script executed:

#!/bin/bash
# 기존 QueryPort와 CommandPort의 메서드 반환 타입 패턴 확인

echo "=== BookApiQueryPort 메서드 확인 ==="
ast-grep --pattern 'interface BookApiQueryPort {
  $$$
}'

echo "=== BookCommandPort 메서드 확인 ==="
ast-grep --pattern 'interface BookCommandPort {
  $$$
}'

echo "=== 다른 QueryPort 인터페이스들의 패턴 확인 ==="
fd -t f -e java | xargs grep -l "QueryPort" | head -5

Length of output: 823


🏁 Script executed:

#!/bin/bash
echo "=== BookApiQueryPort.java ==="
sed -n '1,200p' src/main/java/konkuk/thip/book/application/port/out/BookApiQueryPort.java || true

echo ""
echo "=== BookCommandPort.java ==="
sed -n '1,200p' src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java || true

echo ""
echo "=== AttendnaceCheckQueryPort.java ==="
sed -n '1,200p' src/main/java/konkuk/thip/attendanceCheck/application/port/out/AttendnaceCheckQueryPort.java || true

Length of output: 1389


CQRS Port 분리 컨벤션 위반 확인 필요

BookApiQueryPort에 도메인 엔티티(Book)를 반환하는 loadBookWithPageByIsbn 메서드가 추가되어 있습니다. 학습된 컨벤션에 따르면

  • QueryPort: 조회 API 응답용 DTO/VO 반환
  • CommandPort: 도메인 엔티티 조회용 메서드

현재 구현은 이 컨벤션을 따르고 있지 않으므로 아래 중 하나로 수정이 필요합니다:

  • loadBookWithPageByIsbn(String isbn) 메서드를 BookCommandPort로 이동
  • QueryPort에 남겨야 할 경우, 반환 타입을 도메인이 아닌 DTO/VO 형태로 변경

수정 대상:

  • src/main/java/konkuk/thip/book/application/port/out/BookApiQueryPort.java
  • (이동 시) src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/book/application/port/out/BookApiQueryPort.java
around lines 11 to 13, the method loadBookWithPageByIsbn returns a domain entity
Book, violating the CQRS port separation convention. To fix this, either move
the loadBookWithPageByIsbn method to BookCommandPort.java if it needs to return
the domain entity, or if it must remain in BookApiQueryPort.java, change its
return type to a DTO or VO instead of the domain entity. Adjust the code
accordingly in the specified files to comply with the CQRS convention.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

융통성 챙겨~

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@buzz0331 알겠습니다! 프로젝트의 맥락에 따라 유연하게 적용하는 것이 좋겠네요.

현재 BookApiQueryPort에서 loadBookWithPageByIsbn 메서드가 도메인 엔티티를 반환하는 구조로 두셔도 괜찮습니다. 실제 사용 목적과 팀의 개발 편의성을 고려한 판단이라고 생각합니다.


🧠 Learnings used
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

이 부분은 Service가 Port로부터 알라딘, 네이버 api 의 통신결과에 해당하는 dto가 아니라, Book 도메인 엔티티를 반환받아 그 다음 로직을 수행하는게 어떤가 싶어서 Book 도메인 엔티티를 반환하는 메서드를 추가해두었습니다.

기존 Port 의 네이밍이 BookAPIQueryPort 이고, 해당 port 를 내부에 희진님이 네이버 api 와의 통신 결과에 해당하는 dto 를 반환하는 메서드들을 정의해주셨지만,

  1. Port의 네이밍을 '외부 api와의 통신과정을 통해 Book 애그리거트에 관한 정보를 반환한다' 라는 의미로 BookApiPort 로 수정
  2. 기존 네이버 api 와의 통신결과를 반환하는 메서드의 반환 타입을 Book 도메인 엔티티로 수정 (이건 수정한다면 추후 리펙토링시에 진행해도 될 것 같습니다)

은 어떤지 얘기 나눠보고싶습니다! @buzz0331 @hd0rable

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  1. book 패키지 내부의 application.out에는 BookQueryPort, BookCommandPort, BookApiPort 이렇게 3개의 port로 네이밍하자는 말로 이해했는데 맞나요? 맞다면 좋은 생각인 것 같습니다!
  2. 이 부분이 잘 이해가 안되는데 현재 코드 상에서는 네이버 api의 통신결과로 반환되는 dto(NaverBookParseResult, NaverDetailBookParseResult) 내부에 조회된 책 목록의 List를 가지고 있는 형태인데 이걸 모두 Book으로 바꾼다면 하나씩 naver api로부터 책을 하나씩 가져와서 서비스 클래스에서 List로 합치자는 말씀이신가요..?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

  1. book 패키지 내부의 application.out에는 BookQueryPort, BookCommandPort, BookApiPort 이렇게 3개의 port로 네이밍하자는 말로 이해했는데 맞나요? 맞다면 좋은 생각인 것 같습니다!
  2. 이 부분이 잘 이해가 안되는데 현재 코드 상에서는 네이버 api의 통신결과로 반환되는 dto(NaverBookParseResult, NaverDetailBookParseResult) 내부에 조회된 책 목록의 List를 가지고 있는 형태인데 이걸 모두 Book으로 바꾼다면 하나씩 naver api로부터 책을 하나씩 가져와서 서비스 클래스에서 List로 합치자는 말씀이신가요..?
  1. 맞습니다
  2. 엇 이건 Service 에서 반환받은 dto를 가지고 다시 Book 도메인 엔티티를 만들어서 다음 로직을 수행하는 코드가 있는거 같아 말씀드린 거였습니다!
    그런데 코드를 다시 확인해보니 '책 상세조회' api 와, '저장된 책 상태 변경' api 에서 모두 NaverDetailBookParseResult 를 반환하는 메서드를 활용하는데, 조회 api에서는 이 결과를 바로 반환하고, 변경 api 에서는 이 결과를 Book 도메인 엔티티로 변경하는 플로우네요!

이렇다면 괜찮은 것 같습니다 (다만 query를 위한 dto를 반환하는 메서드와, command 를 위한 도메인 엔티티를 반환하는 메서드가 구분되는게 좀 더 좋지 않나? 라는 생각이 있긴 합니다!)

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ public interface BookCommandPort {
Long save(Book book);

Book findById(Long id);
}

void updateForPageCount(Book book);
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ public BookIsSavedResult changeSavedBook(String isbn, boolean isSave, Long userI
naverResult.description());

Long newBookId = bookCommandPort.save(newBook);
book = newBook.withId(newBookId);

book = bookCommandPort.findById(newBookId);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ㅎㅎ

}

// 유저가 저장한 책 목록 조회
Expand Down
18 changes: 5 additions & 13 deletions src/main/java/konkuk/thip/book/domain/Book.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,19 +55,11 @@ public static Book withoutId(String title, String isbn, String authorName, boole
.build();
}

public Book withId(Long id) {
return Book.builder()
.id(id)
.title(this.title)
.isbn(this.isbn)
.authorName(this.authorName)
.bestSeller(this.bestSeller)
.publisher(this.publisher)
.imageUrl(this.imageUrl)
.pageCount(this.pageCount)
.description(this.description)
.build();
public boolean hasPageCount() {
return pageCount != null && pageCount > 0;
}


public void changePageCount(Integer newPageCount) {
this.pageCount = newPageCount;
}
}
13 changes: 10 additions & 3 deletions src/main/java/konkuk/thip/common/exception/code/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,30 +42,32 @@ public enum ErrorCode implements ResponseCode {
*/
BOOK_KEYWORD_ENCODING_FAILED(HttpStatus.BAD_REQUEST, 80000, "검색어 인코딩에 실패했습니다."),
BOOK_NAVER_API_REQUEST_ERROR(HttpStatus.BAD_REQUEST, 80001,"네이버 API 요청에 실패하였습니다."),
BOOK_NAVER_API_PARSING_ERROR(HttpStatus.BAD_REQUEST, 80002,"네이버 API 응답 파싱에 실패하였습니다."),
BOOK_NAVER_API_PARSING_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, 80002,"네이버 API 응답 파싱에 실패하였습니다."),
BOOK_NAVER_API_URL_ERROR(HttpStatus.BAD_REQUEST, 80003,"네이버 API URL이 잘못되었습니다."),
BOOK_NAVER_API_URL_HTTP_CONNECT_FAILED(HttpStatus.BAD_REQUEST, 80004,"네이버 API 요청 중, HTTP 연결에 실패하였습니다."),
BOOK_NAVER_API_RESPONSE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, 80005,"네이버 API 응답에 실패하였습니다."),
BOOK_SEARCH_PAGE_OUT_OF_RANGE(HttpStatus.BAD_REQUEST, 80006,"검색어 페이지가 범위를 벗어났습니다."),
BOOK_KEYWORD_REQUIRED(HttpStatus.BAD_REQUEST, 80007, "검색어는 필수 입력값입니다."),
BOOK_PAGE_NUMBER_INVALID(HttpStatus.BAD_REQUEST, 80008, "페이지 번호는 1 이상의 값이어야 합니다."),
BOOK_ISBN_NOT_FOUND(HttpStatus.BAD_REQUEST, 80009, "ISBN으로 검색한 결과가 존재하지 않습니다."),
BOOK_NAVER_API_ISBN_NOT_FOUND(HttpStatus.BAD_REQUEST, 80009, "네이버 API 에서 ISBN으로 검색한 결과가 존재하지 않습니다."),
Comment thread
seongjunnoh marked this conversation as resolved.
BOOK_NOT_FOUND(HttpStatus.BAD_REQUEST, 80010, "존재하지 않는 BOOK 입니다."),
BOOK_ALREADY_SAVED(HttpStatus.BAD_REQUEST, 80011, "사용자가 이미 저장한 책입니다."),
DUPLICATED_BOOKS_IN_COLLECTION(HttpStatus.INTERNAL_SERVER_ERROR, 80012, "중복된 책이 존재합니다."),
BOOK_NOT_SAVED_CANNOT_DELETE(HttpStatus.BAD_REQUEST, 80013, "사용자가 저장하지 않은 책은 저장삭제 할 수 없습니다."),
BOOK_NOT_SAVED_DB_CANNOT_DELETE(HttpStatus.BAD_REQUEST, 80014, "DB에 존재하지 않은 책은 저장삭제 할 수 없습니다."),
BOOK_ALADIN_API_PARSING_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, 80015, "알라딘 API 응답 파싱에 실패하였습니다."),
BOOK_ALADIN_API_ISBN_NOT_FOUND(HttpStatus.BAD_REQUEST, 80016, "알라딘 API 에서 ISBN으로 검색한 결과가 존재하지 않습니다."),

/**
* 90000 : recentSearch error
*/
INVALID_SEARCH_TYPE(HttpStatus.BAD_REQUEST, 900000,"알맞은 검색어 타입을 찾을 수 없습니다."),


/**
* 100000 : room error
*/
ROOM_NOT_FOUND(HttpStatus.NOT_FOUND, 100000, "존재하지 않는 ROOM 입니다."),
INVALID_ROOM_CREATE(HttpStatus.BAD_REQUEST, 100001, "유효하지 않은 ROOM 생성 요청 입니다."),

/**
* 110000 : vote error
Expand All @@ -87,6 +89,11 @@ public enum ErrorCode implements ResponseCode {
*/
USER_ROOM_NOT_FOUND(HttpStatus.NOT_FOUND, 130000, "존재하지 않는 USER_ROOM (방과 사용자 관계) 입니다."),

/**
* 140000 : Category error
*/
CATEGORY_NOT_FOUND(HttpStatus.NOT_FOUND, 140000, "존재하지 않는 CATEGORY 입니다.")

;

private final HttpStatus httpStatus;
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/konkuk/thip/config/RestTemplateConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package konkuk.thip.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestTemplateConfig {

@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
Comment on lines +10 to +13
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

프로덕션 환경을 위한 RestTemplate 설정 개선 필요

현재 기본 RestTemplate 설정만 있어 프로덕션 환경에서 성능 및 안정성 문제가 발생할 수 있습니다. 다음 설정들을 고려해보세요:

  • Connection timeout, Read timeout 설정
  • Connection pool 설정
  • Error handling 개선
  • SSL 인증서 검증 설정
 @Bean
 public RestTemplate restTemplate() {
-    return new RestTemplate();
+    HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
+    factory.setConnectionRequestTimeout(5000);
+    factory.setConnectTimeout(5000);
+    factory.setReadTimeout(10000);
+    
+    RestTemplate restTemplate = new RestTemplate(factory);
+    restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
+    return restTemplate;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
public RestTemplate restTemplate() {
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setConnectionRequestTimeout(5000);
factory.setConnectTimeout(5000);
factory.setReadTimeout(10000);
RestTemplate restTemplate = new RestTemplate(factory);
restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
return restTemplate;
}
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/config/RestTemplateConfig.java around lines 10 to
13, the current RestTemplate bean uses default settings which may cause
performance and stability issues in production. Enhance the RestTemplate
configuration by setting connection and read timeouts, configuring a connection
pool using a suitable HttpClient, improving error handling with a custom
ResponseErrorHandler, and ensuring proper SSL certificate validation. Implement
these configurations within the RestTemplate bean method to make it
production-ready.

}
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
package konkuk.thip.room.adapter.in.web;

import jakarta.validation.Valid;
import konkuk.thip.common.dto.BaseResponse;
import konkuk.thip.common.security.annotation.UserId;
import konkuk.thip.room.adapter.in.web.request.RoomCreateRequest;
import konkuk.thip.room.adapter.in.web.response.RoomCreateResponse;
import konkuk.thip.room.application.port.in.RoomCreateUseCase;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class RoomCommandController {

private final RoomCreateUseCase roomCreateUseCase;

@PostMapping("/rooms")
public BaseResponse<RoomCreateResponse> createRoom(@Valid @RequestBody RoomCreateRequest request, @UserId Long userId) {
return BaseResponse.ok(RoomCreateResponse.of(
roomCreateUseCase.createRoom(request.toCommand(), userId)
));
}
}

This file was deleted.

Loading