배달의 민족은 장바구니를 어떻게 만들었을까? (작성중)

신은지·2023년 3월 13일
2

1️⃣ 발단

공차를 주문하려고 배달의 민족에 들어갔다.
블랙 밀크티 펄 추가 당도 100짜리 밀크티를 장바구니에 담다가 문득 궁금해졌다.

🤔 배달 장바구니랑 배민1 장바구니는 따로인가?

2️⃣ 전개

담아봤다. 같은 가게의 메뉴만 담을 수 있다는 경고문이 뜬다.
동일한 가게라도 배달 서비스와 배민1 서비스의 장바구니를 공유할 수 없다.


애초에 배달 서비스 입점 가게와 배민 1 서비스 입점 가게를 다른 가게로 분류하고 있다. 단, 같은 가게라면 함께 주문 서비스는 동일 장바구니를 이용한다.
그럼 B마트는 어떨까?

담아진다. (+ 간편식/밀키트 서비스는 B마트 장바구니에 담아진다.)
그렇다면 배민스토어는?

담아진다. 배달/배민1과 B마트와는 다른 장바구니가 생긴다.
전국 별미도 마찬가지로 별개의 장바구니에 담아진다.
선물하기 서비스는 구매 후 바로 선물로 넘어가기에 장바구니가 따로 없다.

대강 정리해보면 이렇게 된다.

  • 배달 서비스와 배민 1 서비스는 같은 장바구니를 공유한다.
    • 배달 서비스 가게의 메뉴를 장바구니에 담은 경우, 배민 1의 메뉴는 추가 불가능
    • 배달 서비스의 가게와 배민 1 서비스의 가게는 다른 가게로 취급
    • 함께주문 서비스는 배달/배민1 서비스와 동일 장바구니를 사용한다
  • B마트 서비스엔 별도의 장바구니가 존재한다.
  • 배민 스토어 서비스엔 별도의 장바구니가 존재한다.
  • 전국 별미 서비스에도 별도의 장바구니가 존재한다.

장바구니.. 짱많아...!

그럼 이제 몇 가지 의문이 생긴다.

[ 서비스 관련 의문 ]

  • 배민 1은 왜 별도의 장바구니를 갖지 않는가?
    • 배민 1은 나중에 추가된 서비스다.
    • 배민 1은 일반 배달 서비스와는 배달비 책정 방식, 배달 시간, 쿠폰 적용 등등이 전부 다르게 적용된다.
    • 배민 1은 배달 서비스와 별도의 카테고리로 분류된다.
    • B마트, 배민스토어 등과 동일하게 별도의 장바구니를 만들어도 되지 않나?

[ 개발 관련 의문 ]

  • 장바구니 서비스 내부에서 도메인별 장바구니를 구분하는 방법은 뭘까?
    • 배달의 민족은 각 도메인을 마이크로 서비스로 분리한 MSA 구조로 설계되어 있다(고 들었다.)
    • 그럼 장바구니 서비스도 아마 마이크로 서비스로 분리되어 있을 것이다.
  • 특정 도메인 (ex. B마트)에 어떻게 해당 도메인의 장바구니를 제공해줄 수 있을까?
    • API를 분리하나? 근데 그럼 클라쪽에서 도메인별 장바구니 맵핑 로직을 관리해야하는걸?
    • 테이블을 다 분리하나?
    • 아니면 하나의 테이블 안에서 컬럼으로 구분해?
    • RDB가 맞긴 할까?
  • 신규 서비스가 런칭되면 또 장바구니 종류를 추가해야 한다. 기존 장바구니들을 건드리지 않으면서 신규 장바구니를 추가하는 방법은 뭘까?

배달의 민족 (우아한형제들)은 기술 블로그가 정말 잘 되어있다. 한번 장바구니 서비스를 검색해보자.

아쉽지만 장바구니 관련 게시글은 없었다. (근데 왜 페이지 사이즈를 3으로 정한걸까?)
그럼 이제 뇌피셜을 펼쳐보자.

3️⃣ 위기

일단 RDB를 쓴다고 가정하자. 1. 도메인별 관계가 명확하고 2. 스키마가 고정적이기 때문이다. 요구사항은 아래와 같다.

  • 유저는 도메인별 장바구니를 가질 수 있다 (배달, 배민스토어, B마트 등)
  • 각 장바구니는 가게와 1:1 관계를 가진다 (한 장바구니에는 같은 가게 메뉴만 담을 수 있음)
  • 장바구니에는 여러 메뉴가 담길 수 있고, 담을 수 있는 메뉴에 개수 제한은 없다.
  • 장바구니에 담은 메뉴는 당장 주문하지 않더라도 삭제할 때까지 장바구니에 남아있어야 한다.

유저가 도메인별 장바구니를 가지게 하는 방법을 몇 가지 고민해보자.

1. 1:N 관계를 만들고 장바구니 테이블에 도메인 구분 컬럼을 갖게 한다.

깊게 생각 안하고 바로 그려봤다.
boolean 타입의 컬럼으로 어떤 도메인에 해당하는 장바구니인지를 구분하는 방식이다.

괜찮은데? 싶을지 몰라도 문제가 아주 많다.

(1) 새로운 서비스를 런칭하려면 ddl를 이용해야 한다.
기존 저장된 모든 데이터 레코드 구조를 변경해야 한다.
유저의 수 x n개 만큼의 장바구니 레코드가 있을텐데, 이 데이터를 한꺼번에 변경하는 것은 위험하고, 시간이 얼마나 걸릴지도 알 수 없는 일이다.

물론 운영 환경과 동일한 테스트 환경을 만들고 예상 시간을 측정해볼 수도 있지만, 혹시라도 예상치 못한 이슈가 발생할 수 있고, 그로 인해 데이터 무결성이 손상될 수도 있다는 점을 생각하면 딱히 고르고 싶은 선택지는 아니다.

운영 환경에서 발생할 수 있는 모든 잠재적 위험성을 체크하고, 유저들에게 서버 점검 공지를 날리고, ddl을 수행하고... 일단 난 심장이 떨린다ㅋㅋ

(2) 중복 값이 너무 많아진다.
위 예시는 PK, FK, 구분컬럼만 뒀지만 실제로는 더 많은 정보가 필요할 것이다.
구분자 컬럼에 true, false 값을 할당하기 위해 유저별로 구분 컬럼의 개수만큼 레코드가 필요하고, 따라서 그만큼 데이터가 중복된다.

(3) 장바구니를 초기화하는 방식이 더러워진다.

신규 유저가 회원가입을 하면, 모든 도메인별로 장바구니 데이터를 추가해 초기화해야한다. (그래야 유저가 장바구니에 아이템을 바로바로 담을 수 있을테니까)

위 설계에선 케이스별로 CART 생성자를 만들어야하며, 신규 서비스가 런칭이 될 경우 기존 만들었던 생성자들을 전부 수정해줘야 한다.

정적 팩토리 메소드를 이용한 예시. 해당 메소드를 호출하는 클라이언트단을 수정할 필요는 없겠지만, 그래도 팩토리 메소드 하나하나를 수정해줘야 하는건 동일하다.

그래서 이번엔 boolean이 아니라 enum 컬럼을 이용해 장바구니를 구별해봤다.
위에서 발생했던 문제들을 하나하나 되짚어보자.

(1) 새로운 서비스를 런칭하려면 ddl를 이용해야 한다.
=> 해결 안됨
=> 기존 레코드에 값을 Update 쳐 줄 필요는 없지만 enum 선택지를 늘려주기 위해 ddl을 이용해야하는 건 마찬가지다.
=> boolean 사용 방식보단 나아졌지만 여전히 완벽한 대안은 아니다.

(2) 중복 값이 너무 많아진다.
=> 해결 안됨
=> type (도메인) 수 만큼 레코드를 추가해야하므로 데이터 중복은 여전하다.

(3) 장바구니를 초기화하는 방식이 더러워진다.
=> 해결?!

이제 신규 서비스가 런칭되었다고 생성자를 수정할 필요는 없다. Type 값만 넘겨주면 되니까!

도메인별 장바구니 생성하는 것도 간단하다. enum을 순회돌면서 cart를 생성하면 끝.
따라서 enum 클래스에 신규 값만 추가해주면 그 외 코드는 수정하지 않아도 된다.

언뜻 보면 해결된 것 같지만, (1), (2)의 문제가 그대로 남아있다.
그러니 enum을 이용한 1:N 관계는 일단 킵해보자.

2. 장바구니 테이블을 도메인별로 분리한다.

음 일단 테이블이 엄청나게 늘어났다ㅋㅋ

4️⃣ 결말

profile
호그와트 장학생

0개의 댓글