작가 검색 API 구현 완료
구현 개요
- Endpoint:
GET /api/v1/search/authors
- 기능: 검색어에 해당하는 작가 목록과 각 작가의 대표작(최대 2권)을 조회
- 사용 위치: 검색 화면의 '작가' 탭
주요 특징
1. N+1 문제 해결
- 2단계 조회 전략 적용
- 작가 목록 조회 (페이징)
- 작가별 도서 카운트 조회 (GROUP BY)
- 작가별 도서 목록 조회 (IN 쿼리)
- 총 3개의 쿼리로 모든 데이터 조회
@EntityGraph보다 효율적이고 유지보수 용이
2. 페이징 지원
- 클라이언트 요청에 따라 페이지 단위로 조회
- 무한 스크롤 UI 지원
- 최대 페이지 크기: 50
3. 대표작 자동 선정
- 각 작가당 최대 2권의 대표작 표시
- 최신 출판일 기준으로 정렬
- UI 공간 효율성 확보
4. 역할 기반 필터링
BookAuthors.role = AUTHOR만 조회
- 번역가(
TRANSLATOR)는 제외
- 정확한 작가 정보 제공
API 명세
Request
GET /api/v1/search/authors?query={검색어}&page={페이지}&size={크기}
| Parameter |
Type |
Required |
Default |
Description |
| query |
String |
O |
- |
검색 키워드 (작가명) |
| page |
int |
X |
1 |
페이지 번호 (1부터 시작) |
| size |
int |
X |
10 |
페이지 크기 (최대 50) |
Response
{
"page": 1,
"size": 10,
"isEnd": true,
"totalCount": 1,
"items": [
{
"authorId": 42,
"name": "김영하",
"profileImageUrl": "https://example.com/authors/kim-younha.jpg",
"occupation": "소설가",
"nationality": "한국",
"bio": "1968년생 한국 소설가...",
"bookCount": 15,
"books": [
{
"bookId": 1234,
"title": "살인자의 기억법",
"thumbnailUrl": "https://example.com/books/1234.jpg",
"authorNames": ["김영하"],
"publisherName": "문학동네",
"publishedAt": "2013-07-15T00:00:00"
},
{
"bookId": 5678,
"title": "빛의 제국",
"thumbnailUrl": "https://example.com/books/5678.jpg",
"authorNames": ["김영하"],
"publisherName": "문학동네",
"publishedAt": "2006-11-20T00:00:00"
}
]
}
]
}
예외 처리
1. 검색 결과 없음
{
"page": 1,
"size": 10,
"isEnd": true,
"totalCount": 0,
"items": []
}
- 빈 배열 반환 (404 아님)
- 프론트엔드에서 "검색 결과가 없습니다" UI 표시
2. 잘못된 입력값
// 빈 검색어
GET /api/v1/search/authors?query=
→ 400 Bad Request: "검색 키워드는 필수입니다."
// 잘못된 페이지 번호
GET /api/v1/search/authors?query=김영하&page=0
→ 400 Bad Request: "페이지 번호는 1 이상이어야 합니다."
// 페이지 크기 초과
GET /api/v1/search/authors?query=김영하&size=100
→ 400 Bad Request: "페이지 크기는 1~50 사이여야 합니다."
3. 작가는 있지만 도서가 없는 경우
{
"authorId": 99,
"name": "신인작가",
"bookCount": 0,
"books": []
}
- 정상 응답
- UI에서 "등록된 작품이 없습니다" 표시
성능 임계값
- 작가 수 < 1,000: 현재 전략으로 충분 (응답 시간 < 100ms)
- 작가 수 1,000~10,000: 인덱스 튜닝 필요
- 작가 수 > 10,000: Elasticsearch 도입 검토
향후 최적화 예정 후보 목록
-
캐싱 전략
- Redis에 인기 작가 캐싱 (TTL 1시간)
- 검색 결과 캐싱 (TTL 10분)
-
Full-Text Search
- MySQL Full-Text Index 적용
- 또는 Elasticsearch 도입
- 한글 형태소 분석기 추가
-
비동기 처리
- 도서 카운트는 비동기로 조회
- CompletableFuture 활용
주의사항
1. 동명이인 문제
- 현재 ERD는
authors.name에 UNIQUE 제약이 있음
- "김영하(소설가)"와 "김영하(시인)"을 구분 불가
- 단기 해결:
shortIntro 또는 bio에 구분 정보 포함
- 장기 해결: UNIQUE 제약 제거 또는
name_disambiguation 컬럼 추가
2. 부분 검색 성능
- 현재:
LIKE '%keyword%' (전체 검색)
- 문제: 인덱스를 활용하지 못함
- 대안:
LIKE 'keyword%' (접두사 검색) 또는 Full-Text Search
3. bio 길이 제한
- DB:
LONGTEXT (제한 없음)
- API: 200자로 자르고 "..." 추가
- 상세 페이지에서 전체 내용 제공 필요
작가 검색 API 구현 완료
구현 개요
GET /api/v1/search/authors주요 특징
1. N+1 문제 해결
@EntityGraph보다 효율적이고 유지보수 용이2. 페이징 지원
3. 대표작 자동 선정
4. 역할 기반 필터링
BookAuthors.role = AUTHOR만 조회TRANSLATOR)는 제외API 명세
Request
Response
{ "page": 1, "size": 10, "isEnd": true, "totalCount": 1, "items": [ { "authorId": 42, "name": "김영하", "profileImageUrl": "https://example.com/authors/kim-younha.jpg", "occupation": "소설가", "nationality": "한국", "bio": "1968년생 한국 소설가...", "bookCount": 15, "books": [ { "bookId": 1234, "title": "살인자의 기억법", "thumbnailUrl": "https://example.com/books/1234.jpg", "authorNames": ["김영하"], "publisherName": "문학동네", "publishedAt": "2013-07-15T00:00:00" }, { "bookId": 5678, "title": "빛의 제국", "thumbnailUrl": "https://example.com/books/5678.jpg", "authorNames": ["김영하"], "publisherName": "문학동네", "publishedAt": "2006-11-20T00:00:00" } ] } ] }예외 처리
1. 검색 결과 없음
{ "page": 1, "size": 10, "isEnd": true, "totalCount": 0, "items": [] }2. 잘못된 입력값
3. 작가는 있지만 도서가 없는 경우
{ "authorId": 99, "name": "신인작가", "bookCount": 0, "books": [] }성능 임계값
향후 최적화 예정 후보 목록
캐싱 전략
Full-Text Search
비동기 처리
주의사항
1. 동명이인 문제
authors.name에 UNIQUE 제약이 있음shortIntro또는bio에 구분 정보 포함name_disambiguation컬럼 추가2. 부분 검색 성능
LIKE '%keyword%'(전체 검색)LIKE 'keyword%'(접두사 검색) 또는 Full-Text Search3. bio 길이 제한
LONGTEXT(제한 없음)