[feat] #9 - 온보딩 여행 정보 저장 API 구현 - #10
Conversation
sae2say
left a comment
There was a problem hiding this comment.
전체적으로 짜임새가 너무 좋고 읽기가 너무 편했어요!! 역시 대윤아..
다만 카톡으로도 한 번 언급한 것과 같이, 추후 TourAPI를 호출할 때 원활할 수 있도록 초반 설정을 검토해 보는 부분이 필요할 것 같습니다. 이외에는 너무 짱짱이어서 할 말이 업슴니다..
저도 다음 이슈 코드는 윤아님처럼 작성해보려고요 ㅎㅎ 내 코드컨벤션은 대윤아
너무 수고하셨습니다~ 수정하신 후 다시 요청 주세요!
| * 요청 매핑과 파라미터 바인딩 애노테이션은 구현 컨트롤러에 둔다. | ||
| */ | ||
| @Tag(name = "Onboarding", description = "온보딩 여행 기본 정보 API") | ||
| public interface OnboardingControllerDocs { |
sae2say
left a comment
There was a problem hiding this comment.
너무 꼼꼼하게 잘 작업해주셨네요. 많이 배웠숩니다!! 코멘트 몇 개만 확인해주시고, 더 이상 변경사항 없으면 더 이상은 코리 요청 없이 바로 머지해주셔도 될 것 같습니다. 수고하셨습니다!
| TRADITIONAL_MARKET(TourApiContentType.SHOPPING, "전통 시장", of("SH", "SH06")), | ||
| LOCAL_SHOP(TourApiContentType.SHOPPING, "로컬 상점", of("SH", "SH05")), | ||
| DUTY_FREE(TourApiContentType.SHOPPING, "면세점", of("SH", "SH04")), | ||
| // TODO: 정의서에 빈티지와 1:1로 대응되는 lclsSystm 코드가 없어 contentType만 보관한다. |
There was a problem hiding this comment.
좋네요~ TourAPI에 바로 사용가능하도록 대분류는 기존의 TourAPI 분류체계 대분류를 그대로 따르는 것이 아닌, contentType을 기준으로 하고 중분류는 그대로 따름으로써 이제 검색도 원활하고, 사용자 맞춤 추천도 가능할 것 같습니다! 좋아요!
| @Enumerated(EnumType.STRING) | ||
| @Column(name = "region") | ||
| private Region region; | ||
| @ManyToOne(fetch = FetchType.LAZY) |
There was a problem hiding this comment.
member 필드에는 optional = false로 설정하시고 여기는 default로 두신 이유가 있나요? (궁금띠해서)
There was a problem hiding this comment.
member의 경우에는 여행일정의 소유자이기 때문에 항상 존재해야해서 optional을 false로 설정하였습니다! 반면 region의 경우에는 일정 선택시 지역 미정 플로우에서 region이 null일 수 있어 기본값으로 설정해두었습니당!
| public static TravelRegion create( | ||
| final String displayName, | ||
| final String lDongRegnCd, | ||
| final String lDongSignguCd | ||
| ) { | ||
| return TravelRegion.builder() | ||
| .displayName(displayName) | ||
| .lDongRegnCd(lDongRegnCd) | ||
| .lDongSignguCd(lDongSignguCd) | ||
| .build(); | ||
| } | ||
| } |
There was a problem hiding this comment.
이 정팩메는 언제 쓰일 걸 생각하고 만드신건가요?
There was a problem hiding this comment.
저희 서비스에서는 지역 정보를 db에 직접 넣어주면 될 것 같아서 정적팩토리메서드가 필요가 없겠네요....! 삭제하겠습니당
Related issue 🛠
Work Description ✏️
온보딩에서 수집한 여행 기본 정보를 바탕으로 첫 번째 여행 일정(
TravelPlan)을 생성하는POST /api/onboardingAPI를 구현했습니다.기존에는 온보딩 정보를 회원당 1개만 가질 수 있는
TravelProfile형태로 저장했지만, 한 회원이 여러 여행 일정을 가질 수 있어야 하므로 구조를 변경했습니다.성공 시
201 Created와 함께SuccessResponse<OnboardingCompleteResponse>를 내려주며, 응답에는 생성된 여행 일정 ID만 포함됩니다.{ "travelPlanId": 1 }역할 변경 및 토큰 재발급은 온보딩 API에서 처리하지 않고, 기존에 구현된
PATCH /api/auth/roleAPI가 담당하도록 분리했습니다.TourAPI 기준 취향 구조 정리
기존
TravelCategory/TravelSubcategory구조는 우리 서비스 UX 기준이라 TourAPI KorService2의contentTypeId,lclsSystm1/2/3와 맞지 않았습니다.따라서 온보딩 취향 상위 기준을
TourApiContentType으로 변경했습니다.TOURIST_ATTRACTION(12)CULTURAL_FACILITY(14)FESTIVAL_EVENT(15)LEISURE_SPORTS(28)SHOPPING(38)RESTAURANT(39)TRAVEL_COURSE(25),LODGING(32)도 enum에는 포함했지만 온보딩 취향 선택에서는 제외했습니다.요청 예시는 다음과 같습니다.
{ "contentType": "RESTAURANT", "subcategories": ["KOREAN_FOOD", "CAFE_TEAHOUSE"] }각
TravelSubcategory에는 TourAPI 검색에 사용할 수 있도록contentType과lclsSystm1/2/3매핑 정보를 추가했습니다.매핑 값은
신분류체계정보 관광타입정보 연계 정의서.xlsx의분류체계_관광타입맵핑시트를 기준으로 확인한 값만 반영했습니다.지역 구조 변경
기존
Region enum은 제거했습니다.디자인상 기본 지역 외에도 “기타” 선택 후 지역 검색이 가능해야 하므로, 지역은 enum이 아니라 DB 테이블로 관리하도록 변경했습니다.
추가된 엔티티:
기타 지역 검색 API도 추가했습니다.
keyword가 비어 있으면 빈 목록을 반환합니다.기본 지역 목록은 프론트 화면에서 관리하고, 백엔드는 기타 지역 검색과 온보딩 저장 시
regionId검증만 담당합니다.온보딩 요청의 지역 필드도 enum이 아니라
regionId기준으로 변경했습니다.{ "regionId": 1, "regionUndecided": false }검증 분리
단일 필드 검증은 Bean Validation(
@NotNull,@NotEmpty)으로, 필드 간 교차 검증은OnboardingRequestValidator로 분리했습니다. 컨트롤러에는 검증 로직을 두지 않았습니다.regionId와regionUndecided는 정확히 하나만 성립Asia/Seoul기준)contentType에 속해야 함TRAVEL_COURSE,LODGING은 온보딩 취향 선택 불가LIKE_ALL_FOOD는RESTAURANT내에서 단독 선택만 허용Trouble Shooting ⚽️
1. 날짜 검증의 테스트 가능성
"시작일은 오늘 이후"를
LocalDate.now()로 짜면 테스트에서 시간을 고정할 수 없습니다.ClockConfig로Clock빈을 등록하고 validator가 이를 주입받게 해서, 테스트에서Clock.fixed()로 특정 날짜를 고정할 수 있도록 했습니다. 시점 기준은ZoneId.of("Asia/Seoul")로 고정했습니다.2. TourAPI 취향 분류 매핑
기존 UX 기준 대분류(
FOOD,NATURE,CULTURE등)는 TourAPI의contentTypeId와 1:1로 맞지 않았습니다.예를 들어 기존
CULTURE안에는 문화시설, 관광지, 축제/공연/행사 성격이 섞여 있어 TourAPI 검색 조건으로 쓰기 어렵습니다.그래서
TravelCategory는 제거하고, TourAPI 검색 기준인TourApiContentType을 온보딩 취향의 상위 기준으로 사용했습니다.lclsSystm1/2/3값은 추측하지 않고 첨부 엑셀 정의서에서 확인한 코드만 넣었습니다.공식 코드와 1:1 매핑이 애매한 항목은
contentType만 보관하거나 TODO로 남겼습니다.예:
VINTAGEFIREWORKSNIGHT_MARKETHIKING3. 온보딩과 토큰 재발급 책임 분리
온보딩 건너뛰기 기능이 있고, role 변경으로 인한 토큰 재발급 API가 이미 따로 구현되어 있어 온보딩 API 응답에서 토큰을 제거했습니다.
현재 책임은 다음처럼 분리했습니다.
4. 지역 enum 제거
기타 도시 검색이 필요한 화면 구조라서
Region enum에 도시를 계속 추가하는 방식은 확장성이 낮다고 판단했습니다.따라서 지역은
TravelRegion테이블에서 관리하고, 온보딩 요청은regionId를 받도록 변경했습니다.다만
lDongRegnCd/lDongSignguCd는 실제 TourAPI KorService2 연동에 쓰일 값이라 임의로 seed하지 않았습니다. 정확한 법정동 코드 데이터 주입 방식은 추후 정해야 합니다.Related ScreenShot 📷
정상적으로 완료된 경우

Uncompleted Tasks 😅
travel_region초기 데이터 seedTravelRegion의 정확한lDongRegnCd/lDongSignguCd데이터 확보travel_profile → travel_plan,profile_id → travel_plan_id마이그레이션contains외 초성/공백/정렬 등)To Reviewers 📢
TravelPlan)으로 저장하도록 변경했습니다.Member 1 : N TravelPlan구조가 이후 일정 생성/조회/수정 흐름에도 맞는지 확인 부탁드립니다.PATCH /api/auth/role을 호출하는 흐름이 맞는지 확인 부탁드립니다.TravelRegion테이블로 관리하도록 변경했습니다. 기본 지역은 프론트에 노출하고, 백엔드는 기타 지역 검색만 담당하는 방향이 적절한지 의견 부탁드립니다.lclsSystm1/2/3매핑은 첨부 엑셀 기준으로 확인한 값만 넣었습니다. 애매한 항목을 억지 매핑하지 않고 TODO/contentType only로 남긴 판단이 괜찮은지 봐주세요.OnboardingRequestValidator라는 별도@Component로 뺐습니다. 커스텀 어노테이션(ConstraintValidator) 방식보다 명시적인 validator가 낫다고 판단했는데, 이 방향이 괜찮은지 확인 부탁드립니다.