Insight/회고
[업무회고] RIZZ 3.0 유저의 병원별 추천 코드를 통한 맴버십 관리
싯벨트
2025. 1. 12. 09:40
728x90
배경
병원 맴버십앱으로 피봇팅을 결정한 뒤, 가장 핵심이 되는 개념 두 가지는 “단골”과 “추천”이었습니다. 병원은 특정 유저가 본인들의 단골임을 구분할 수 있어야 했고, 유저는 본인의 추천코드를 통해 다른 사람을 해당 병원의 단골로 초대할 수 있어야 했습니다. 또한 고객은 병원을 여러군데 다닐 수 있기 때문에 추천코드도 여러 개를 가질 수 있어야 했습니다. 또한 병원들도 자체적인 추천코드를 가지고 있어서, QR 이나 팜플렛 등을 통해 내원한 고객에게 앱 설치 시 간편한 단골 등록 경험을 줄 수 있어야 했습니다.
최종적으로 정리된 요구사항은 다음과 같습니다.
- 유저는 여러 곳의 병원에 단골로 등록 가능하다
- 유저는 단골인 병원별로 개별적인 추천 코드를 가질 수 있다.
- 유저는 본인의 추천코드를 통해 친구에게 특정 병원을 추천할 수 있다.
- 추천 코드를 통해 가입한 유저는 해당 병원의 단골로 등록된다.
- 유저는 맴버십 가입을 통한 포인트를 한번만 받을 수 있다.
- 유저는 신규가입 유저를 초대했을 때만 초대 포인트를 받을 수 있다.
- 유저는 본인을 추천한 유저를 포함하여 초대한 친구의 목록을 확인할 수 있다.
- 유저는 단골 등록된 병원의 정보만 앱에서 확인할 수 있다.
- 병원은 추천 코드를 가질 수 있다.
기술스택
사용한 기술스택은 다음과 같습니다.
- Typescript
- AWS RDS - MySQL
- AWS SecretManager
- AWS SNS
- AWS SQS
- AWS Cognito
- AWS Lambda
- AWS SAM
도메인 설계
1차 접근
도메인
- Referral
추천인에 대한 정보를 담는 모델로 구성했습니다. 타입은 코드의 주인이 파트너인지, 유저인지를 구분하기 위함이고, 어떤 병원의 추천코드인지를 담기 위해 clinicId를 구성했습니다. ReferralRelation은 에그리게이트 루트인 Referral을 통해 저장될 수 있으며, 이는 add 메서드를 통해 구현했습니다. - ReferralRelation
추천 관계를 나타내는 모델입니다. 가입한 유저가 어떤 코드를 통해 추천되었는지를 담당합니다. 이때, 특정 병원에 추천된 유저의 경우 동일한 병원에 추천을 받으면 안 되기 때문에 이러한 정보를 체크하기 위해 clinicId와 deduplicationKey를 구성하여 추천 이력을 체크했습니다. 또한, 추천 관계를 맺은 친구들의 수를 보여줘야 했기 때문에 isRecipientActive 필드를 추가했습니다. 이는 유저가 탈퇴했을 때 deactivate를 호출하며 비활성화 됩니다. - User
isClinicSet을 통해 단골 병원 선택 여부를 나타냅니다. 이를 통해 클라이언트에서 단골 병원 선택에 따른 분기처리를 할 수 있습니다. - FavoriteClinic
단골 병원들을 등록할 수 있고, 병원에서 확정을 할 수 있도록 했습니다. 에그리게이트 루트인 User의 confirmClinic() 메서드를 통해 확정됩니다.
1차 접근 회고
- 컨벤션
FavoriteClinic이 단골이라는 의미를 제대로 담고 있지 못하다고 생각했습니다. 단골이란 맴버십에 가입했는지를 의미하므로 membership 모델을 생성하기로 결정했습니다. 추천 관계라는 개념이 다소 이상했습니다. 추천 자체가 추천에 대한 관계까지 나타내는 게 더 합당하다고 생각했고, 유니크한 추천 코드 생성 등에 대한 요건을 생각했을 때 코드 자체를 따로 분리하여 ReferralCode 모델을 구성했습니다. - 설계
유저 모델이 단골에 대한 정보를 보여주는 것, 그리고 단골 병원과 구성 관계로 연결될 필요가 없어 보였습니다. 단골이라는 개념에 대한 변경사항이 발생했을 때, 유저 모델을 건드려야 했습니다. 또한 현재 설계대로라면 파트너가 단골 유저를 조회하는 과정에서도 에그리게이트 루트인 유저를 거쳐야 했습니다. 이런 상황을 막기 위해 컨벤션 부분에서 생각했던 membership 모델이 단골에 대한 개념을 책임지도록 했습니다.
추천 관계에서 유저 탈퇴에 대한 비활성화 여부를 체크하는 것도 불필요한 기능이라고 생각했습니다. 유저 테이블 조회를 방지하기 위함이었으나, 정합성 문제가 발생할 수도 있고 동기적으로 처리할 시 탈퇴 로직이 불필요하게 무거워지기에 유저 아이디 리스트를 통해 유저 테이블을 조회하는 방식으로 변경을 생각했습니다. 유저가 입력한 추천코드의 주인과 본인의 코드로 가입한 유저들 중 유효한 유저들만 불러오거나 카운트를 하는 방식으로 접근했습니다.
2차 접근
도메인
- Referral
추천을 담당하는 모델입니다. 유저가 어떤 코드로 추천을 받았는지를 나타내며, 중복제거키를 통해 동일한 병원의 추천을 받는 케이스를 방지합니다. 추천코드를 통해 단골 등록 및 확정이 될 때 추천코드 생성을 하므로 ReferralCode와 Membership을 일시적으로 참조합니다. - ReferralCode
ReferralCode는 추천코드를 담당하는 모델입니다. 코드의 오너는 누구이고, 어떤 병원과 연결되었는지를 나타냅니다. - Membership
Membership은 맴버십을 담당하는 모델입니다. 유저가 어떤 병원의 맴버십에 가입했으며, 맴버십 상태는 어떠한지를 나타냅니다.
기능 시퀀스 - 가입 시 추천 코드 등록
핵심인 추천 코드를 통해 맴버십에 가입하는 케이스를 살펴보겠습니다.
- Referral 모델에서 추천코드를 매개변수로 추천 생성 로직을 호출하면 맴버십 확정을 하는 메서드가 실행됩니다. 메서드는 Membership 레포지토리를 통해 맴버십 생성을 하고, 코드로 인한 가입이기 때문에 확정까지 해줍니다. 유저는 맴버가 되었기 때문에 추천코드를 가질 수 있고, 코드 생성 시 기존 코드와 중복을 막기 위해 트랜잭션 처리를 하여 락을 걸어주고 생성을 완료합니다.
- 유저의 추천을 받았던 이력을 확인하고, 없다면 단골 확정 포인트를 위한 SNS 를 publish 합니다. 충전에 대한 SQS가 이를 subscribe하고, cash 모델이 이를 다시 subscribe 하여 각 병원마다 설정한 포인트를 유저에게 부여합니다.
- 그리고 추천 코드의 주인이 유저인 경우, 추천한 유저에게도 위와 동일한 방식으로 포인트를 지급합니다.
2차 접근 회고
- 도메인 모델
관련 도서에서 기획자, 도메인 모델 전문가와 회의에서 자주 사용되는 개념을 설정하라는 조언이 있었는데 그 부분을 적용하지 못했었습니다. 처음 접근을 맴버십, 추천코드 등을 에그리게이트로 설정하는 것으로 시작했으면 더 깔끔한 설계가 가능했을 것 같습니다.
도메인 모델 설계 시, 모델들의 책임과 협력을 명확하게 설정하는 것이 중요한데 이 부분에서 부족했었습니다. 책임이 불분명하거나 혼합되어 있다면 컨벤션은 물론이고, 요구사항의 변경을 반영할 때 어디를 수정해야 할지 파악하는 것에도 허들이 생기기 때문인데.. 그대로 경험했네요.
기존에 클라이언트가 호출하는 api에 추가적인 정보를 담아서 처리를 편하게 해주기 위한 접근을 하다보니 isClinicSet 과 같은 필드를 추가하게 되었습니다. 좋은 의도이지만 도메인 관점에서 먼저 생각하고, 그 다음 제공할 수 있는 부분을 고려해야겠습니다.