29CM TEAM
Published in

29CM TEAM

예약하기 서비스 개발기

기존 서비스의 한계 안에서 예약하기 서비스를 개발하며 겪은 해결방법과 배운점을 공유합니다. 비슷한 고민을 하고 계신 분들께 도움이 되길 바랍니다.

예약하기 서비스란?

호텔 상품을 예약하는 흐름 예시 (왼쪽에서 오른쪽으로 흐름)

배경

서비스화의 배경
29CM은 셀렉트샵으로, 지금까지 다양한 콘텐츠를 통해 유형상품을 소개하고 구매할 수 있는 경험을 제공하였습니다. 최근, 29CM에서 판매하였던 티켓과 숙박상품들의 큰 판매성과를 통해 29CM 서비스 내 독립적인 비즈니스 경쟁력이 있다고 판단하였고, 이를 서비스화하게 되었습니다.

기존 서비스가 지닌 한계
이미 언급한것 처럼 29CM의 서비스는 유형상품을 고려하여 설계되어 있었기 때문에, 상품 도메인에서 예약 상품정보를 관리하는 것이 어렵다고 판단하였습니다. 특히 데이터 구조상 단순히 컬럼추가와 테이블 추가로 해결하기엔 서비스 복잡도만 높아질 것으로 예상하여 독립적인 독립된 데이터베이스에 데이터를 관리하는 서비스로 개발하였습니다

용어

개발에 대한 내용으로 들어가기전, 혼동을 줄이기 위하여 반복되는 단어를 정리합니다.

테이블 구조

  • 기존 서비스에서 상품과 옵션테이블: 유형상품의 데이터 구조로, 상품테이블에는 모든 메타데이터(상품명, 노출가격, 할인율, 고시정보등, 이미지, 총 재고 수량 등의 정보)를 포함합니다. 옵션테이블에는 옵션명, 추가가격, 옵션의 재고수량의 정보만 저장합니다.
  • 예약 서비스에서 상품테이블과 옵션테이블: 유형상품의 옵션테이블의 메타데이터를 저장할 수 없는 한계를 개선하고자, 상품테이블은 옵션의 정보를 묶어서 보여주는 카달로그 개념과 비슷하게 설계하려고 하였습니다.신규 상품테이블에는 숙박정보(대표가격, 대표이미지, 노출 숙박상품명, 고시정보, 노출 상태)만 저장합니다. 신규 옵션테이블에는 실제 판매할 상품의 핵심정보인 객실정보(객실명, 객실상태, 객실상세정보, 객실 상세이미지 정보등의 대부분의 메타데이터)를 저장합니다.

어드민

  • 기존 어드민: 29CM의 모든 어드민 기능을 제공합니다.
  • (신규) 어드민: 예약 상품을 관리하기 위하여 신규로 오픈한 어드민입니다.

서비스

  • 상품 서비스 : 상품 정보를 제공하고 관리하는 서비스입니다.
  • 주문&결제 서비스 : 결제수단을 관리하며, 주문서, 주문처리를 하는 서비스입니다. 다른 도메인(상품, 회원, 쿠폰, 마일리지, 결제)의 데이터베이스에 직접 질의하도록 개발되어 있는 한계가 존재합니다.
  • (신규) 예약 상품 서비스 : 예약 상품을 위한 정보를 저장하고 제공하는 서비스입니다. 독립적인 DB에 상품 정보를 저장합니다.
  • (신규) 주문 서비스 : 주문&결제 서비스가 비대해질 것을 고려하여 주문 도메인을 위한 서비스를 신규로 개발하였습니다. 특히 기존 주문&결제 서비스가 다른 도메인 데이터에 직접 질의하는 문제를 해결하는데 중점을 두었습니다.
좌) 기존 서비스 구조, 우) 신규 서비스가 추가된 구조

예약 서비스를 개발하는데 기존 서비스가 지닌 한계와 해결방안

예약하기 서비스를 개발하는데 어떤 한계가 있었고, 어떻게 해결하였는지 총 4개항목으로 나누어 소개합니다.

1. 기존 옵션테이블에는 옵션의 메타데이터를 저장하기 어려움

옵션테이블은 개별적인 옵션정보를 저장하기 때문에 컬럼추가 또는 데이터 반정규화를 통해 객실별 일정, 상세설명, 상세이미지를 저장하기 어려운 한계가 존재합니다.

해결방법 : 신규 옵션 테이블이 중심 메타테이블이 되고, 신규 상품테이블은 옵션을 묶어 주는 역할을 담당

기존에는 상품 테이블에 대부분의 상품 정보가 저장되고, 옵션테이블에 추가금액등 빈약한 정보만 가지는 구조였지만, 신규 상품 테이블에서는 최소한의 메타정보만 갖고, 옵션테이블의 정보를 묶어주는 역할의 데이터만 저장하도록 하였습니다.

예약 상품 서비스 ERD

2. 어드민에서 예약 상품 날짜관리의 한계

기존 어드민에서는 날짜를 표현하는데 한계가 있었습니다. 아래처럼 객실과 날짜를 조합해서 옵션 상품들이 만들어지기 때문에, 예약 가능한 날짜가 조금이라도 늘어나게되면 옵션 상품들이 무수히 늘어나게된다는 한계가 있었습니다

29CM 어드민 상품등록 화면 중 옵션 설정영역
네이버 쇼핑 상품등록 화면

해결방법: 복잡한 일정은 테이블 정규화

옵션테이블에 객실정보를 저장하고, 객실의 일정, 상세설명 그리고 이미지를 별도의 테이블로 정규화하였습니다.

예약 상품 서비스 ERD중 옵션테이블

이후 정규화한 일정정보를 옵션별로 캘린더에 표시하고 직관적으로 수정이 가능해졌습니다.

신규 어드민에서는 옵션별 캘린더로 날짜를 관리
저장된 일정, 상세설명, 이미지들이 상품상세에서 표현되는 상품상세 페이지

3. 상품정보 수정요청과 주문요청의 경합이 발생

기존 옵션테이블에서는 재고수량이 컬럼으로 저장되고 있었습니다.이는 유저의주문시 재고를 차감하는 요청과 어드민에서 상품을 수정하는 요청시 Lock경합으로 주문처리의 성능저하가 발생할 수 있습니다.

기존 옵션 테이블에 컬럼으로 관리되는 재고수량

해결방법: 메타 데이터와 재고값을 정규화

재고 테이블을 별도로 정규화하여 경합을 줄이도록 하였습니다. 그리고 옵션 테이블과 사은품 테이블에서 재고테이블의 기본키를 외래키로 참조하도록 설계하였습니다.

예약 상품 서비스 ERD 중 재고, 옵션, 그리고 사은품 테이블

재고 테이블이 옵션 테이블의 기본키를 외래키로 가지는 것이 아니라, 반대로 만들어진 이유는 여러개의 사은품을 한개 재고수량값으로 관리하고 싶다는 요구사항 때문입니다. 예를 들어, 구매 사은품으로 와인 또는 샴페인을 제공하는데, 총량으로만 관리하는 것입니다.

여러 사은품을 한 개의 수량으로 관리

4. 상품상세에서 날짜 표현의 한계

상품상세 조회화면에서 날짜의 연박선택이 불가능하고, 긴 리스트로 표현되어 구매경험이 떨어집니다.

예시) 예약하기 서비스 오픈전 기존서비스에서 날짜를 표현

해결방법: 일정을 캘린더로 표현
일정은 캘린더로 표현하였고, 당연히 연박도 가능하도록 하였습니다.

상품상세 화면에서 날짜를 선택하는 화면

신규 서비스가 가진 한계

1. 분산시스템을 위한 유일 ID 설계를 하였으나, 실행에 옮기지 못한 한계

29CM 서비스에서는 상품테이블의 기본키로 노출되고, 참조되는 곳이 아주 많습니다. 고객문의, 리뷰, 브랜드, 쿠폰, 이벤트, 피드, 포스트 서비스 등 다양한 기능에서 상품테이블의 기본키를 외래키로 저장하고 있습니다.

예시) 상품을 큐레이션하는 영역에서 상품 기본키를 활용

외부에 상품번호가 그대로 노출되면, 크롤링으로 외부에서 비지니스 규모를 예측할 수 있게 되고, 마이크로서비스 확장에 제약이 생깁니다.

따라서 이를 개선하고자 ULID(Universally Unique Lexicographically Sortable Identifier)형태의 대체키를 생성하여 분산 시스템하기 위한 설계를 하였습니다.

ULID를 활용한 상품번호 대체키 테이블

하지만, 사용자 행동분석 툴인 Amplitude에 상품테이블의 기본키로 적재된 것을 알게 되었고, 마이그레이션이 어려워 아쉽지만 유일ID를 서비스에 적용하는 것을 중단하였습니다.

결국 예약 서비스에서 상품을 등록하더라도 기존 상품테이블에서 기본키를 생성하고, 해당 키값을 신규 예약 서비스에서 값을 저장하게 하였습니다.

2. 예약하기 서비스를 통해 상품 등록시 유형상품 서비스에 데이터를 싱크 해주어야 하는 한계

유일ID로 상품을 노출하려고 하였으나, 많은 한계가 존재했기 때문에 차선책으로 결국 상품 노출의 하위 호환성을 지키기 위하여 기존 상품 테이블에 예약 상품을 저장하게 하였습니다.

전시영역에서 상품이 노출되는 화면 (상품명, 이미지, 브랜드명, 이름, 할인율, 가격, 좋아요 개수)

예약 상품 서비스와 상품 서비스간 호출 방향과 방법을 설정해야했습니다.

예약서비스에서는 상품서비스에서 제공하는 REST API를 호출하고, 상품서비스에서는 메시지를 발행하고 예약 상품 서비스에서는 이를 구독하여 요청을 처리합니다.

좌) API 동기 호출, 우) 메시지 비동기 통신

개발 과정에서 배운점

1. 수정 Request의 리스트 데이터타입에 신규정보와 수정정보가 섞일 경우 JPA에 save 메서드에 그대로 위임하여 손쉽게 생성과 수정을 처리

데이터 수정 API 호출 시 기존데이터는 id필드에 기본키를, 신규 데이터는 null값을 보내도록 하였습니다. JPA는 persist할 때 ID 유무로 수정과 등록을 구분하기 때문에 이를 활용하면 구현이 단순하고 코드가 깔끔해집니다.

아래는 어드민에서 상품 이미지 수정화면입니다. 오른쪽 영역은 수정 API Request payload 이고 id필드의 값이 다른 것을 볼 수 있습니다.

기존이미지는 ID값 존재, 신규이미지는 ID값 NULL

해당 로직을 간단한 pseudo code로 작성하면 아래와 같습니다.

2. ADR문서를 활용한 프로젝트 결정 내용 정리

프로젝트를 진행하며 논의가 필요한 대부분의 협의는 ADR(Architecture Decision Record)로 정리하였습니다. 기획, 디자인, 개발, 데이터설계, 아키텍쳐설계 등 다양한 영역에 대한 논의를 문서화하였습니다. 향후 커뮤니케이션 비용을 줄이고, 의사결정 히스토리를 파악하는데 매우 유용합니다.

ADR문서 모음

3. 헥사고날 아키텍쳐 적용시 장점과 단점 경험

개인적으로 프러덕션 서비스에 처음으로 헥사고날 아키텍쳐를 적용하였고 아래와 같은 패키지 구조로 사용하였습니다. 백엔드팀에서는 표준구현 아키텍쳐가 있었기 때문에, Application layer에 InOut 패키지를 추가하였습니다. 어플리케이션 레이어에서 참조하는 모든 객체를 Domain으로 옮기고, 기존의 Entity는 Persist에만 사용하도록 하였습니다.

헥사고날 아키텍쳐 다이어그램

장점: Domain객체를 사용하여, Entity에 존재하지 않는 속성값을 활용할 수 있고 특히 중간 계산이 많은 주문의 경우 중간 계산값을 저장하고 여러 로직에서 반복하지 않아도 되기 때문에 성능개선에 매우 유용하였습니다.

도메인객체가 Entity와 일치하지 않아도 되는 매우 큰 장점.

단점: DirtyChecking을 사용할 수 없고, Entity객체가 Domain 객체가 아니기 때문에 Read/Write할때 마다 매핑을 해주는 번거로움이 있습니다.

repository를 통해 저장하기 전과 후로 매핑

4. 복잡한 상태관리를 Enum을 활용한 State Machine으로 구현

상품 상태의 변경가능 플로우가 아래처럼 복잡할 경우 어떻게 처리해야할까요? 처음에는 State Design Pattern을 사용하여, State Interface와 이를 구현한 상태클래스들로 구현하려고 하였습니다. 하지만 과다하게 클래스들이 많아지고 복잡해지면서 Enum을 활용한 유한상태 머신을 만들었습니다. 상태별 전이 가능로직에 어드민별 권한 조건까지 추가되었지만 Enum만으로 모든 관리가 되어 깔끔합니다.

5. 단순한 비지니스를 위한 배치는 Quartz를 사용

SpringBatch를 세팅하기엔 시간이 부족하고, Batch 처리 로직을 API endpoint로 만들고 젠킨스 job에서 trigger하기엔 배치 실행시작 시간의 타이밍의 제약이 있었습니다. 이를 빠르게 해결하기 위해 Quartz를 사용하였습니다. 특히 단순한 값을 변경하거나, 이벤트를 발행하는데 유용하였습니다.

특히, Trigger, JobDetail 식별자를 명시적으로 할당하여 중복 등록되지 않도록 하는것이 중요합니다.

아래와 같이 Job을 scheduler에 등록하면 정해진 주기에 일정하게 실행됩니다.

프로젝트기간이 길었기에 깊이있는 내용보다는 가볍게 전반적인 프로젝트 과정을 소개해드렸습니다.

프로젝트 런칭을 위해 고생해주신 안가람, 서수민, 권형주, 정상협, 송보은, 김가을, 우지혜, 이지민님 감사합니다. 그리고 프로젝트 마무리에 큰 도움주신 이경석, 정태훈, 강호길, 유가희, 이희창님 감사합니다.

예약하기 프로젝트 맴버

이 글이 신규 기능을 개발을 위해 Trade-off를 고민하시는 분들께 조금이나마 도움이 되었으면 좋겠습니다 🤗

함께 성장할 동료를 찾습니다

29CM (무신사) 는 3년 연속 거래액 2배의 성장을 이루었습니다.

이제 더 큰 성장을 위해 기존 모놀리틱 서비스 구조를 마이크로서비스 구조로 전환하고, 앵귤러 기반 프론트엔드 코드를 리엑트로 전환하는 등의 기술적인 시도를 진행하고 있습니다.

함께 성장하고 유저 가치를 만들어낼 동료 개발자분들을 찾습니다
많은 지원 부탁합니다!

--

--

29CM | 이십구센티미터를 만드는 모든 이야기를 담습니다.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store