⚓ DATT 13 - Place 리뷰, 평점 시스템과 사용자 반응 기반 추천 구조
개요
현재 DATT는 다음 기능들을 제공한다.
1
2
3
4
장소 검색
Nearby Search
Bookmark
Anchor 자동 큐레이션
하지만 이것만으로는:
1
"사용자 반응"
을 알 수 없다.
예를 들어:
1
2
3
이 장소가 실제로 좋은가?
사람들이 만족했는가?
어떤 Anchor가 반응이 좋은가?
등을 판단할 수 없다.
따라서 이번 단계에서는:
1
2
Place 리뷰/평점 시스템
Anchor 좋아요 시스템
을 구축하였다.
핵심 목표는:
1
사용자 반응 데이터 축적
이다.
왜 Place 품질 데이터가 필요한가
처음에는 단순:
1
공공데이터 기반 장소 제공
만으로도 충분할 수 있다고 생각했다.
하지만 실제 서비스에서는 큰 문제가 생긴다.
예를 들어:
1
2
3
폐업 직전 가게
평점이 매우 낮은 장소
실제 만족도가 낮은 장소
등도 그대로 노출될 수 있다.
즉:
1
2
3
공공데이터
≠
좋은 장소
이다.
결국 중요한 것은 사용자 반응이다
실제 사용자 서비스에서는:
1
2
3
4
5
조회수
좋아요
리뷰
평점
저장 수
등이 훨씬 중요하다.
왜냐하면:
1
사용자 행동 데이터
는:
1
"실제 사람들이 좋아했는가"
를 보여주기 때문이다.
즉:
1
2
데이터 자체보다
사용자 반응이 더 중요하다.
그래서 리뷰/평점 시스템을 도입했다
이번 단계에서는:
1
PlaceReview
구조를 도입하였다.
핵심은:
1
사용자 경험 기록
이다.
PlaceReview Entity 설계
최종 구조는 다음과 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Entity
@Table(
name = "place_review",
uniqueConstraints = {
@UniqueConstraint(
name = "uk_place_review_member_place",
columnNames = {"member_id", "place_id"}
)
}
)
public class PlaceReview extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
private Member member;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
private PlaceMaster placeMaster;
private int rating;
private String content;
}
왜 리뷰와 평점을 분리하지 않았는가
처음에는 다음 구조도 고민했다.
1
2
PlaceRating
PlaceReview
즉:
1
평점만 남기는 구조
이다.
하지만 MVP 단계에서는 복잡도가 너무 커진다.
예:
1
2
3
4
평점만 남긴 사용자
리뷰도 남긴 사용자
평점 수정 정책
리뷰 생성 시 기존 평점 처리
등.
그래서 리뷰 안에 평점을 포함시켰다
최종적으로는:
1
리뷰 작성 = 평점 작성
구조로 설계했다.
즉:
1
2
3
4
{
"rating": 5,
"content": "데이트하기 분위기가 좋았습니다."
}
처럼 동작한다.
이 방식의 장점은:
1
2
3
구조 단순화
중복 정책 단순화
사용자 흐름 단순화
이다.
왜 사용자당 리뷰 1개만 허용했는가
중요한 정책이다.
이번 구조에서는:
1
2
3
한 사용자
=
한 장소당 리뷰 1개
만 허용한다.
즉:
1
member_id + place_id
에 Unique Constraint를 걸었다.
이유는 무엇인가
만약 중복 리뷰를 허용하면:
1
2
3
리뷰 스팸
평점 조작
의미 없는 반복 리뷰
문제가 발생한다.
또한 MVP 단계에서는:
1
리뷰 수정
만으로 충분하다.
즉:
1
후기 갱신
개념으로 보는 것이 자연스럽다.
평점 정책
현재 평점 범위는:
1
1 ~ 5
이다.
도메인 내부에서 직접 검증한다.
1
2
3
4
5
private void validateRating(int rating) {
if (rating < 1 || rating > 5) {
throw new BusinessException(ErrorCode.INVALID_REVIEW_RATING);
}
}
즉:
1
잘못된 상태 자체를 생성하지 못하게
막았다.
Place 평점 집계 구조
이번 단계에서는:
1
2
averageRating
reviewCount
를 제공한다.
예:
1
2
3
4
{
"averageRating": 4.5,
"reviewCount": 12
}
왜 실시간 집계 방식으로 구현했는가
처음에는:
1
PlaceMaster에 컬럼 저장
도 고려했다.
예:
1
2
average_rating
review_count
하지만 현재 MVP에서는:
1
2
트래픽 규모
리뷰 규모
가 크지 않다.
따라서 우선은:
1
2
AVG(rating)
COUNT(*)
기반 실시간 집계로 구현했다.
나중에 어떻게 확장 가능한가
사용자 수가 증가하면:
1
캐시 컬럼 반정규화
방식으로 확장 가능하다.
예:
1
2
3
리뷰 생성
→ 평균 평점 갱신
→ PlaceMaster 저장
즉 현재는:
1
단순성 우선
이다.
Place Detail API와의 연동
리뷰/평점은:
1
Place Detail API
에 연동하였다.
즉 장소 상세 조회 시:
1
2
3
4
{
"averageRating": 4.5,
"reviewCount": 12
}
정보를 함께 내려준다.
왜 Place 품질 데이터가 중요한가
이 데이터는 단순 UI 정보가 아니다.
향후:
1
2
3
4
추천 시스템
랭킹
인기 장소
Anchor 추천 품질
등의 핵심 데이터가 된다.
예:
1
2
평점 높은 장소 우선 추천
리뷰 수 많은 장소 가중치 증가
등.
즉:
1
2
3
Place 품질 데이터
=
추천 엔진 기반
이다.
Anchor 좋아요 시스템 설계
Place와 별개로:
1
AnchorLike
도 도입하였다.
왜 Anchor에도 좋아요가 필요한가
DATT의 핵심은:
1
경험 큐레이션
이다.
즉 중요한 것은:
1
"이 장소 하나가 좋은가?"
만이 아니다.
오히려:
1
"이 경험 흐름이 좋은가?"
가 더 중요하다.
예:
1
2
3
성수 데이트 코스
강릉 여행 코스
홍대 술집 탐방
등.
AnchorLike Entity 설계
최종 구조는 다음과 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Entity
@Table(
name = "anchor_like",
uniqueConstraints = {
@UniqueConstraint(
name = "uk_anchor_like_member_anchor",
columnNames = {"member_id", "anchor_id"}
)
}
)
public class AnchorLike extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
private Member member;
@ManyToOne(fetch = FetchType.LAZY)
private Anchor anchor;
}
왜 좋아요를 별도 Entity로 분리했는가
처음에는:
1
Anchor.likeCount++
같은 단순 카운팅도 고려했다.
하지만 좋아요 여부는:
1
사용자별 상태
가 중요하다.
예:
1
2
좋아요 눌렀는가?
좋아요 취소했는가?
등.
따라서:
1
Member ↔ Anchor 관계
를 별도 Entity로 모델링했다.
좋아요 응답 구조
현재 Anchor 응답에는 다음 정보가 포함된다.
1
2
3
4
{
"likeCount": 12,
"isLiked": true
}
즉:
1
2
3
좋아요 수
+
현재 사용자 좋아요 여부
를 동시에 제공한다.
인기 Anchor 구조
이번 단계에서는:
1
인기 Anchor 조회
기반도 구축하였다.
정렬 기준은:
1
2
3
조회수
좋아요 수
최신성
이다.
즉:
1
실제 반응이 좋은 경험
을 우선 노출한다.
사용자 반응 기반 추천 구조
현재 DATT는 단순:
1
공공데이터 제공
수준이 아니다.
핵심은:
1
사용자 반응 데이터 축적
이다.
현재 축적되는 데이터는 다음과 같다.
1
2
3
4
5
Bookmark
리뷰
평점
AnchorLike
조회수
왜 이 데이터가 중요한가
이 데이터들은 이후:
1
2
3
4
추천 시스템
개인화 추천
랭킹
인기 지역 탐색
등의 기반이 된다.
예:
1
2
3
평점 높은 장소 추천
좋아요 많은 Anchor 추천
저장 수 기반 인기 지역 추천
등.
즉:
1
2
3
사용자 반응 데이터
=
서비스 핵심 자산
이다.
현재 단계에서 의도적으로 제외한 것들
이번 단계에서는 다음 기능은 제외했다.
1
2
3
4
5
리뷰 이미지
리뷰 신고
리뷰 좋아요
AI 추천
실시간 트렌드 분석
이유는 단순하다.
현재 중요한 것은:
1
MVP 수준 데이터 축적 구조
를 만드는 것이기 때문이다.
현재 구조의 장점
현재 구조는 다음 장점을 가진다.
1
2
3
4
단순하다
확장 가능하다
추천 시스템으로 발전 가능하다
사용자 행동 데이터를 축적할 수 있다
특히 중요한 점은:
1
"사용자 행동 자체를 데이터로 본다"
는 것이다.
앞으로의 확장 방향
향후에는 다음 방향으로 확장 가능하다.
1. 리뷰 기반 추천
예:
1
비슷한 취향 사용자 추천
2. Anchor 추천 점수 고도화
예:
1
2
3
4
좋아요
저장 수
조회수
평점
조합.
3. 개인화 추천
예:
1
사용자 리뷰 성향 기반 추천
4. AI 기반 요약
예:
1
2
리뷰 요약
분위기 분석
마무리
이번 단계에서는:
1
2
3
Place 리뷰/평점
AnchorLike
사용자 반응 데이터
구조를 구축하였다.
핵심은:
1
2
"공공데이터 자체보다
사용자 반응 데이터를 더 중요하게 본다"
는 것이다.
DATT는 앞으로:
1
2
3
4
5
탐색
경험
평가
추천
개인화
를 중심으로 발전할 예정이다.