⚓ DATT 11 - Bookmark 시스템 설계 및 사용자 저장 기능 모델링 전략
개요
현재 DATT는 다음 기능들을 지원하고 있다.
1
2
3
4
장소 검색
Nearby Search
Place Detail API
JWT 인증
하지만 사용자 기반 서비스가 되기 위해서는:
1
2
3
4
저장
개인화
공유
큐레이션
기능이 반드시 필요하다.
특히 DATT는 단순 지도 앱이 아니라:
1
2
3
4
5
데이트
약속
여행
동선
경험 큐레이션
서비스를 목표로 하고 있기 때문에:
1
"사용자가 장소를 저장한다"
는 매우 중요한 기능이 된다.
따라서 이번 단계에서는:
1
Bookmark 시스템
을 구축하였다.
Bookmark 기능이 중요한 이유
처음에는 Bookmark를 단순 저장 기능으로 생각하기 쉽다.
하지만 실제 서비스에서는 Bookmark가:
1
개인화 데이터
의 시작점이 된다.
예를 들어 사용자는:
1
2
3
4
좋아하는 장소 저장
데이트 장소 저장
가고 싶은 장소 저장
Anchor 생성용 장소 저장
등을 수행한다.
즉 Bookmark는 단순 기능이 아니라:
1
사용자 취향 데이터
가 된다.
DATT에서 Bookmark의 역할
DATT에서 Bookmark는 다음 기능들과 연결된다.
1
2
3
4
Anchor 생성
장소 공유
개인화 추천
Gamification
즉 Bookmark는:
1
DATT 사용자 경험의 핵심 데이터
가 된다.
Bookmark 시스템 설계
이번 단계에서 설계한 핵심 구조는 다음과 같다.
1
2
3
4
5
Member
↕
PlaceBookmark
↕
PlaceMaster
즉:
1
2
사용자
↔ 장소
관계를 Bookmark Entity로 관리한다.
PlaceBookmark Entity
최종 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
@Entity
@Table(
name = "place_bookmarks",
uniqueConstraints = {
@UniqueConstraint(
name = "uk_place_bookmark_member_place",
columnNames = {"member_id", "place_id"}
)
}
)
public class PlaceBookmark extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "member_id", nullable = false)
private Member member;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "place_id", nullable = false)
private PlaceMaster placeMaster;
}
왜 ManyToMany를 직접 사용하지 않았는가
JPA를 처음 사용할 때 가장 쉽게 떠올리는 방식은:
1
@ManyToMany
이다.
예:
1
2
@ManyToMany
private List<PlaceMaster> bookmarks;
하지만 실제 서비스에서는 이 방식이 거의 항상 문제가 된다.
ManyToMany의 문제점
1. 확장성이 매우 낮다
Bookmark는 시간이 지나면 다음 정보들이 필요해진다.
1
2
3
4
5
6
저장 일시
메모
공개 여부
폴더
정렬 순서
공유 상태
하지만 @ManyToMany는:
1
중간 테이블에 추가 필드 확장
이 어렵다.
2. 비즈니스 로직이 애매해진다
실제 Bookmark는 단순 관계가 아니다.
1
"사용자가 장소를 저장한 행위"
라는 의미를 가진다.
즉 Bookmark 자체가:
1
독립적인 도메인
이 된다.
따라서 별도 Entity로 분리하는 것이 더 자연스럽다.
3. 유지보수가 어려워진다
@ManyToMany는 내부적으로:
1
숨겨진 중간 테이블
을 생성한다.
문제는 시간이 지나면:
1
2
3
쿼리 최적화
삭제 정책
확장 정책
등을 제어하기 어려워진다.
그래서 선택한 구조
최종적으로 다음 구조를 선택하였다.
1
2
3
4
5
Member
↕
PlaceBookmark
↕
PlaceMaster
즉:
1
중간 관계 Entity 직접 관리
전략이다.
북마크 중복 방지 전략
Bookmark에서는:
1
같은 장소 중복 저장
을 막아야 한다.
이를 위해:
1
2
3
4
@UniqueConstraint(
name = "uk_place_bookmark_member_place",
columnNames = {"member_id", "place_id"}
)
를 적용하였다.
즉:
1
member_id + place_id
조합은 하나만 존재 가능하다.
왜 DB 레벨에서도 막았는가
Service에서만 검증하면:
1
동시성 문제
가 발생할 수 있다.
예:
1
동시에 두 번 저장 요청
이 들어오면 중복 저장될 수 있다.
따라서:
1
2
3
Service 검증
+
DB Unique Constraint
를 함께 적용하였다.
Bookmark API 설계
이번 단계에서는 다음 API를 구축하였다.
북마크 추가 API
1
POST /api/bookmarks/places/{placeId}
역할:
1
현재 로그인 사용자의 장소 저장
북마크 삭제 API
1
DELETE /api/bookmarks/places/{placeId}
역할:
1
저장한 장소 삭제
내 북마크 목록 조회 API
1
GET /api/bookmarks/places
역할:
1
내가 저장한 장소 목록 조회
왜 /places 를 붙였는가
초기에는:
1
/api/bookmarks/{placeId}
형태도 고려했다.
하지만 이후:
1
Anchor Bookmark
도 추가될 가능성이 높다.
따라서:
1
2
/api/bookmarks/places
/api/bookmarks/anchors
처럼 리소스를 명확히 분리하였다.
PlaceBookmarkResponse 설계
북마크 응답 DTO는 다음 구조를 가진다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public record PlaceBookmarkResponse(
Long bookmarkId,
Long placeId,
String bizesNm,
String brchNm,
String indsMclsCd,
String indsMclsNm,
String ctprvnNm,
String signguNm,
String adongNm,
String rdnmAdr,
Double lon,
Double lat,
LocalDateTime bookmarkedAt
)
왜 Place 정보도 함께 응답하는가
북마크 목록은 실제로:
1
카드형 UI
로 렌더링된다.
즉 프론트에서는:
1
2
3
4
장소명
주소
업종
좌표
등이 필요하다.
따라서:
1
2
3
Bookmark 자체 정보
+
Place 정보
를 함께 내려주도록 설계하였다.
Bookmark 여부 조회 기능
이번 단계에서는:
1
isBookmarked
기능도 추가하였다.
즉 Place Detail API에서:
1
현재 로그인 사용자가 저장한 장소인지
확인 가능하다.
Place Detail API 연동
최종적으로 Place Detail 응답은:
1
Boolean isBookmarked
필드를 포함한다.
즉 프론트에서는:
1
북마크 버튼 상태
를 즉시 렌더링 가능하다.
인증 사용자 연동
Bookmark는:
1
로그인 사용자 기반 기능
이다.
따라서:
1
@AuthenticationPrincipal
을 사용하여 현재 인증 사용자를 가져오도록 구현하였다.
JWT 기반에서도 가능한가?
가능하다.
중요한 것은 JWT 자체가 아니라:
1
SecurityContext에 Authentication 등록
여부다.
즉 JWT Filter에서:
1
2
SecurityContextHolder.getContext()
.setAuthentication(authentication);
를 수행하면:
1
@AuthenticationPrincipal
사용이 가능하다.
개인화 서비스 구조의 시작점
Bookmark는 단순 저장 기능이 아니다.
실제로는:
1
사용자 취향 데이터
가 된다.
예:
1
2
3
어떤 지역을 저장하는가
어떤 업종을 좋아하는가
어떤 Anchor를 만드는가
등이 모두 데이터로 축적된다.
향후 확장 방향
현재 Bookmark는 MVP 수준이다.
하지만 이후 다음 기능들로 확장 가능하다.
1. Bookmark 폴더
예:
1
2
3
4
데이트
여행
맛집
술집
별 저장 분리.
2. 공개 Bookmark
예:
1
내 저장 리스트 공유
3. Anchor 연동
예:
1
Bookmark 기반 Anchor 생성
4. 개인화 추천
예:
1
사용자 취향 기반 장소 추천
마무리
이번 단계에서는:
1
Bookmark 시스템
을 구축하며:
1
사용자 저장 기능
기반을 완성하였다.
핵심은:
1
2
Bookmark를 단순 관계가 아닌
독립적인 도메인으로 본 것
이다.
현재 구조는:
1
2
확장 가능하고
개인화 서비스로 발전 가능한 구조
를 목표로 설계하였다.
이후 DATT의 핵심 기능인:
1
2
3
4
Anchor
공유
Gamification
개인화 추천
등이 Bookmark 시스템 기반으로 확장될 예정이다.