⚓ DATT 06 - Place 도메인 및 장소 검색 구조 설계
개요
DATT 프로젝트 2주차에서는 장소(Place) 도메인 및 검색 구조를 중심으로 설계를 진행하였다.
1주차에서는 인증/보안 기반 구조를 구축하였다면,
2주차부터는 실제 서비스 핵심 데이터인 장소 데이터를 어떻게 저장하고 검색할 것인지에 대한 구조 설계가 핵심이었다.
특히 이번 작업에서는 다음 내용을 중심으로 정리하였다.
- 공공데이터 기반 Place 도메인 설계
- PlaceMaster 구조 도입 이유
- 장소 검색 API 구현
- 검색 Query 구조 설계
- 검색 성능 개선 방향
왜 Place가 아니라 PlaceMaster인가
초기에는 단순히 Place Entity를 바로 만드는 방향을 고려하였다.
하지만 DATT는 일반적인 맛집 서비스와 달리:
1
공공데이터 기반 장소 데이터 수집
구조를 목표로 하고 있었다.
즉 현재 구조는:
1
2
3
4
공공데이터
→ Batch 적재
→ PlaceMaster 저장
→ 서비스 기능에서 활용
흐름이다.
따라서:
1
공공데이터 원본 저장용 도메인
과:
1
실제 서비스 기능용 Place
는 역할이 다르다고 판단하였다.
그래서 현재는:
1
PlaceMaster
라는 이름으로 공공데이터 기반 장소 마스터 테이블을 먼저 구축하였다.
PlaceMaster Entity 설계
현재 PlaceMaster는 공공데이터 원본 컬럼을 최대한 유지하는 방향으로 설계하였다.
핵심 필드는 다음과 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private String bizesId;
private String bizesNm;
private String brchNm;
private String indsLclsCd;
private String indsLclsNm;
private String indsMclsCd;
private String indsMclsNm;
private String indsSclsCd;
private String indsSclsNm;
private String ctprvnNm;
private String signguNm;
private String adongNm;
private String ldongNm;
private String lnoAdr;
private String rdnmAdr;
private Double lon;
private Double lat;
왜 공공데이터 컬럼을 그대로 유지했는가
처음에는 서비스용 카테고리 Enum을 바로 만들까 고민했다.
예:
1
2
3
CAFE
RESTAURANT
BAR
하지만 현재 단계에서는:
1
실제 공공데이터 업종 값 분석이 먼저
라고 판단하였다.
예를 들어 실제 데이터가:
1
커피전문점/카페
일 수도 있고:
1
커피음료
일 수도 있다.
즉 실제 데이터를 충분히 분석하지 않은 상태에서 서비스 카테고리를 먼저 추상화하면:
1
너무 이른 추상화
가 될 가능성이 높다.
따라서 현재는:
1
공공데이터 원본 구조 보존
을 우선하였다.
왜 PlaceCategory Enum을 보류했는가
처음에는 다음 구조를 고려하였다.
1
2
3
4
5
public enum PlaceCategory {
CAFE,
RESTAURANT,
BAR
}
하지만 실제 공공데이터 업종 체계를 충분히 확인하지 않은 상태에서:
1
서비스 카테고리 정규화
를 먼저 진행하는 것은 위험하다고 판단하였다.
현재 기준으로는:
1
2
3
indsLclsNm
indsMclsNm
indsSclsNm
를 그대로 저장한 뒤,
이후 실제 데이터를 분석하면서:
1
서비스 카테고리 정책
을 별도로 설계하는 방향으로 변경하였다.
장소 검색 API 구현
2주차에서는 기본 장소 검색 API도 구현하였다.
현재 구조는:
1
GET /api/place-masters?keyword=스타벅스
형태로 구성하였다.
검색 흐름은 다음과 같다.
1
2
3
4
keyword 요청
→ Repository 검색
→ DTO 변환
→ 응답 반환
Service:
1
2
3
4
5
6
7
8
9
public List<PlaceMasterSearchResponse> searchByKeyword(
String keyword
) {
return placeMasterRepository
.findByBizesNmContaining(keyword)
.stream()
.map(PlaceMasterSearchResponse::from)
.toList();
}
왜 Entity를 직접 반환하지 않았는가
검색 응답은 DTO 기반으로 구성하였다.
즉:
1
Entity 직접 반환 X
구조다.
이유는 다음과 같다.
- API 응답 스펙 분리
- Entity 변경 영향 최소화
- 불필요한 필드 노출 방지
- 검색 응답 경량화
따라서 검색 응답은:
1
PlaceMasterSearchResponse
를 통해 반환하도록 구성하였다.
검색 Query 구조 설계
현재 검색 구조는 단순한:
1
findByBizesNmContaining()
수준이다.
하지만 이후 확장을 고려하여:
1
검색 조건 객체화
방향을 미리 고려하였다.
예상 검색 조건:
1
2
3
4
5
6
7
keyword
지역
업종
좌표
반경
정렬
페이징
즉 이후에는:
1
동적 Query 기반 검색 구조
로 확장될 예정이다.
왜 처음부터 QueryDSL을 도입하지 않았는가
현재 단계에서는 우선:
1
기본 검색 흐름 검증
이 목적이었다.
따라서:
- QueryDSL
- Elasticsearch
- PostGIS
같은 고급 검색 기술은 아직 도입하지 않았다.
현재 우선순위는:
1
정상 동작 가능한 검색 구조
였기 때문이다.
즉:
1
2
3
단순 구조
→ 동작 검증
→ 이후 최적화
순서로 접근하고 있다.
검색 성능 개선 방향
현재 검색은:
1
LIKE '%keyword%'
기반이다.
즉 데이터가 많아질수록 검색 비용이 증가한다.
따라서 이후 다음 방향들을 고려하고 있다.
1. Index 최적화
예상 인덱스 대상:
1
2
3
4
bizes_nm
ctprvn_nm
signgu_nm
inds_lcls_nm
2. Pageable 기반 검색
현재는 단순 List 반환이지만 이후:
1
Pageable
기반으로 전환 예정이다.
3. Elasticsearch
다음 기능이 필요해지면 Elasticsearch 도입을 고려하고 있다.
- 자동완성
- 오타 허용 검색
- 유사 검색
- 검색 랭킹
- 인기 검색
4. Redis Cache
반복 조회가 많은:
- 인기 장소
- 지역 기반 조회
- 인기 검색어
등은 Redis Cache 적용을 고려 중이다.
5. PostGIS
현재 좌표 저장은:
1
2
Double lon;
Double lat;
기반이다.
하지만 이후:
1
거리 계산 최적화
가 필요해지면:
1
PostGIS
도입을 검토하고 있다.
Place 검색 구조에서 중요하게 본 점
이번 Place 도메인 설계에서 가장 중요하게 본 기준은 다음이었다.
1
과도한 추상화를 피할 것
이었다.
즉:
- 실제 데이터 확인 전 서비스 카테고리 추상화 X
- 처음부터 Elasticsearch 도입 X
- 처음부터 PostGIS 도입 X
- 먼저 단순 구조로 동작 검증
방향을 우선하였다.
현재 단계의 목적은:
1
공공데이터 기반 장소 플랫폼의 기반 구축
이기 때문이다.
테스트 코드 작성
검색 구조는 테스트 코드 기반으로 검증하였다.
검증 대상:
- PlaceMaster 저장
- bizesId 중복 확인
- 상호명 기반 검색
- Service 검색 흐름
테스트 메서드는 기존과 동일하게 BDD 스타일을 사용하였다.
1
2
3
void givenPlaceMaster_whenSave_thenPersistPlaceMaster()
void givenKeyword_whenSearchByKeyword_thenReturnPlaceMasters()
마무리
2주차를 진행하면서 단순 CRUD 이상의 설계 관점을 많이 고민하게 되었다.
특히:
- 원본 데이터 보존
- 서비스 추상화 시점
- 검색 확장 가능성
- 검색 성능
- Batch 기반 적재 구조
등을 실제로 고려하면서 설계를 진행하였다.
현재는 아직:
1
검색 가능한 장소 플랫폼 기반
수준이지만,
이후:
- Elasticsearch
- 추천 시스템
- 지도 기반 검색
- 인기 장소
- 리뷰/저장 기능
등으로 확장할 수 있는 기반 구조를 구축한 상태다.