생각하며 기획하는 커머스 백엔드
강의 활용
개념도를 그리는 툴: draw.io
생각하는 방법
회사의 상황을 꼭 생각하고 비즈니스를 이해하고 소프트웨어를 작성 하도록 노력한다.
- 이 기능이 도움이 되는건지?
요구사항 느끼기’를 다 본 후 잠시 강의를 멈추고 내 생각을 정리해보기
- 요구사항에서 더 필요한 부분이나 제가 정의한 것과 다르게 생각하는 부분이 있는지
- 애초에 기획이 잘못 됐거나 아쉬운 부분이나 누락은 없는지 등등 충분히 생각 해보기
- 우리가 기획자는 아니지만 기획을 통해 비즈니스를 구현을 하는 역할로써 구현할 대상을 충분히 이해했는지 시간을 갖는 목적
’코드 느끼기’ 전에 나는 이렇게 구현할 것 같다 라고 생각해보기 OR 직접 구현해보기 (사실 이게 제일 좋다고 생각합니다ㅎㅎ.. 진짜 코드아니더라도, 상세한 구현 없이 껍데기만 있는 sudo 코드라도 작성해보는 느낌이요!)
- ‘코드 느끼기’ 시청 후 내가 생각한 부분과의 차이점, 아쉬운점 등등 한번더 곱씹기
’개념 정리’ 를 통해 ‘요구사항 느끼기’와 ‘코드 느끼기’ 를 한번더 생각해보기
-
추가로 우리가 요구사항과 다르게 코드 단에서 개념을 정의했거나, 우리 소프트웨어에서 어떤 부분이 더 중요한 개념이고 핵심 개념인지에 대해서 한번 더 생각해보기
-
나름대로 요구사항과 코드에 대한 생각이 있었다면 직접 나만의 개념도를 그려보기
읽어볼만한 커뮤니티 내용
부가 기능을 이벤트 핸들러로 분리하는 기준
비즈니스와 비즈니스 로직의 응집도를 우선적으로 고려한다. 포인트 지급 같은 부가 기능을 별도 이벤트로 발행하면 응집도가 떨어진다.
비즈니스적으로 리뷰 작성과 포인트 지급은 개념적으로 가까운 관계에 속한다. (논리적으로 “포인트는 글로벌하게 쓰이니 이벤트로 처리해도 되는 것 아닌가?”라는 관점도 틀리지는 않다.)
이벤트 기반 처리를 오용하면 응집이 깨진다. 그래서 일반적으로 비즈니스적 맥락이 완전히 분리될 때를 기준으로 이벤트 기반으로 처리한다. (예: 이메일 발송, 알림톡 발송, callback 전송 등 완전히 다른 비즈니스/기능적 맥락)
약간 다른 관점으로, 동시성 관점에서 후처리나 비신뢰 처리가 되어도 된다면 @Async 를 사용하기도 한다.
강의 내용 정리

상품 목록

기획 내용에 대한 질문 없이 API 개발 후 협업 직군에게 스펙 전달 하는 것은 최악이다
이 요구사항을 정리 해보자면?
- 카테고리를 클릭 했을 때 상품 목록을 표시해야 한다.
명확히 기재 해야 할 요구사항은 무엇인가?
- 카테고리 상품은 1대1 매핑 구조인가?
- 상품 목록을 표시할 때 어떤 순서대로 보여주어야 하는가?
- 상품 별 정렬 기준
- 만약, 가장 많이 팔린 순서라고 한다면 판매 갯수를 Product 테이블에 비정규화 하여 저장 할 것인가?
- 페이지 처리 기준
- 상품 별 정렬 기준
코드 느끼기
- 카테고리 테이블 설계 시 자기 참조 구조 설계 -> 계층 표시 목적
- 상품 별 정렬 기준: 가장 많이 판매 된 순서
- ProductSummary 에 product_id, toal_sell_count, total_sell_amount, updated_at, created_at 사용
- 페이지 처리 기준: LIMIT / OFFSET 방식 사용
- 한 페이지 당 20개 표기
- 페이지 캐싱 적용
- 상품을 더 이상 판매 하지 않는다면 상태 값을 변경하여 표시 되지 않도록 변경하기
개념도

상품 상세

이 요구사항을 정리 해보자면?
- 상품의 정보를 표시해야 한다.
- 리뷰 개수와 점수를 표시해야 한다.
- 다운로드 받을 수 있는 쿠폰 목록을 표시해야 한다.
- 상품 상세의 직접적인 렌더링 되는 컨텐츠를 표시해야 한다.
- 상품 상세 페이지에서 찜하기/리뷰 보기/질문을 볼 수 있어야한다.
명확히 기재 해야 할 요구사항은 무엇인가?
- 상품 상세는 오로지 글로만 되어있는가? -> 쿠팡처럼 이미지가 렌더링 되지는 않는가?
코드 느끼기
- 클라이언트에게 API 반환 데이터를 제공할 때, 한 개의 Response 에 속성을 모두 추가하여 제공할 것인가, API 를 분리해서 여러번 호출할 것인가?
- 재사용성을 높이기 위해서는 분리하는 편이 낫다고 생각
- 분리 기준은 상품(상품 정보, 상세 정보, 리뷰 정보) 과 쿠폰으로 분리 -> 상품 상세 API 를 호출 했는데, 리뷰 까지는 OK 이 상품에 대한 정보를 보고 싶을 때 쿠폰은 전혀 다른 개념이지 않을까
- 상품 도메인이 직접적으로 리뷰나 쿠폰을 알게된다면 상품은 모든 내용을 알고 있게된다. 또한, 상품에 대한 개념을 봤을 때 내 상품은 리뷰가 존재하고, 쿠폰이 존재해 라는 개념을 전혀 몰라도 상품은 등록될 수 있어야한다. -> 상품 명칭, 가격, 설명만 존재하더라도 상품은 온전하게 잘 쓰이고있음
- 이런 격벽 외 도메인을 참조하기 위해서 컨트롤러에서 다른 서비스를 참조하여 조합하고, 반환 값을 정의한다.
개념도

리뷰

이 요구사항을 정리 해보자면?
- 평점과 리뷰 내용을 사용자에게 보여줘야 한다.
- 리뷰가 여러개일 때 무한 스크롤 방식을 적용한다.
명확히 기재 해야 할 요구사항은 무엇인가?
- 상품 1개당 1개의 리뷰를 작성하는가?
- 리뷰는 수정 또는 삭제가 가능하는가?
- 리뷰는 글만 존재하거나, 사진 과 글이 같이 존재하는가?
- 제품을 구매한 후 리뷰를 남긴 다음 환불한다면 리뷰는 어떻게 되는가?
- 작성자가 회원을 탈퇴하는 경우 리뷰도 삭제 되는가?
코드 느끼기
- 리뷰를 단순하게 상품만 고려하지 않고, 확장성을 고려하여 Enum 으로 TargetType, TargetId 를 관리
- 리뷰만의 고유한 키를 생성하여 관리하고, 유니크 제약 적용 -> 주문 1개에 리뷰 1개를 매핑하기 위해 이러한 구조를 차용
- 최근 14일 간 주문한 내역만 리뷰를 허용 -> 오래된 주문에 대한 리뷰는 막는 정책 적용
- 리뷰 수정은 7일 간 허용하고, 최종 값만 저장하고 있지만 리뷰로 인한 논쟁이 있다면 리뷰에 대한 이력을 보관해야 한다.
개념도

Q&A

이 요구사항을 정리 해보자면?
- 사용자는 질문을 남길 수 있다.
- 질문을 남긴 글에 답변을 달 수 있다.
명확히 기재 해야 할 요구사항은 무엇인가?
- 상품을 구매하지 않은 사용자도 질문을 남길 수 있는가?
- 질문에 대한 답변을 여러 번 달면 어떻게 할 것인가?
- 질문에 대한 공개 범위를 지정할 것인가?
코드 느끼기
- Question, Answer 를 하나의 QnA 라는 객체로 묶어서 관리
- 이렇게 관리하는 경우 상품 하나에 1개의 질문과 1개의 답변이 강제 되고, 이후 추가 질/답은 다른 질문에서 다루게 된다. -> 1:N 이 되지 않는 구조
개념도

찜하기

이 요구사항을 정리 해보자면?
- 제품 목록을 조회 한 결과 화면에서 찜하기 버튼을 노출한다.
- 제품에 대해 찜하기를 클릭하면, 고객이 찜했던 목록을 불러온다.
명확히 기재 해야 할 요구사항은 무엇인가?
- 찜 한 제품이 판매자에 의해 삭제 되었다면 어떻게 처리할 것인가?
코드 느끼기
- 클라이언트에서 “따닥” 으로 API 가 빠르게 2 번 호출되어, 찜 등록 -> 찜 해제로 UI 상태와 DB 구조가 꼬이는 현상이 발생할 수 있다.
- toggle 식이 아닌, 클라이언트가 보내는 상태(Enum) 값으로 요청을 받도록 수정
개념도

포인트

이 요구사항을 정리 해보자면?
- 포인트 잔액을 보여준다.
- 포인트 적립 & 사용 이력을 보여준다.
명확히 기재 해야 할 요구사항은 무엇인가?
- 리뷰를 작성하고 포인트가 적립 된다면, 리뷰 작성 후 삭제하면 포인트는 어떻게 되는건지? -> 어뷰징 가능
- 포인트 사용 가능한 유효기간이 있는건지?
코드 느끼기
- Point 잔액과 이력을 제공하는 것은 PointService 가 담당
- 포인트를 지급하고 차감하는 것은 PointHandler 가 담당
- 하지만, Claude 로 설계 방식을 질문했을 때 PointService 가 모두 담당하고 포인트 지급을 호출해야 하는 도메인이 Handler 를 만들어서 두 개의 도메인을 조합하는 것을 권장함
쿠폰

다운로드 받을 수 있는 쿠폰은 무엇인지? 부터 요구사항을 다시 정의할 필요가 있다.
- 이미 받은 쿠폰도 보여줘야할까?
- 쿠폰은 고정적인 유효기간이 있는지, 다운로드 받은 후 유효기간이 발생하는지
- 쿠폰은 정률제인지, 정액제인지
- 쿠폰이 적용 가능한 상품/카테고리 등이 별도로 지정 되는지
이 요구사항을 정리 해보자면?
- 유저가 다운로드 받은 쿠폰을 표시한다.
명확히 기재 해야 할 요구사항은 무엇인가?
- 다운로드 받은 쿠폰의 유효기간을 표기 해줘야 하는가?
- 기 만료된 쿠폰도 노출 해야 하는가?
코드 느끼기
- 전체 쿠폰 목록과 내가 소유한 쿠폰 목록을 분리해서 관리한다.
- 전체 쿠폰 목록에서 내가 소유한 쿠폰 목록도 포함해서 노출함
- 쿠폰이 다운로드 될 때 내가 소유한 쿠폰 목록이 생성 되어 관리된다.
개념도

장바구니

- 상품 수량 변경 시 변경된 금액은 API 가 반환 해야 하는가?
- 수량 변경 API 는 증분, 감소 형태로 사용할 것인지, 개수를 작성해서 보낼 것인지?
주문

사용자가 결제하기 버튼을 누르면 결제를 하기 위한 페이지를 보여준다. 페이지에서 결제 하고자 하는 상품을 결제 시 쿠폰과 포인트를 사용할 수 있고
이 요구사항을 정리 해보자면?
- 유저가 다운로드 받은 쿠폰을 표시한다.
명확히 기재 해야 할 요구사항은 무엇인가?
- 포인트 사용 제한이 있는지?
- 쿠폰은 여러개를 한 번에 사용할 수 있는지?
기술적 고민은 무엇인가?
- 주문에 대한 데이터는 언제 생성할 것인가? 결제 전 / 결제 시
- 재고 변경 후 유효성 검증은 결제 후 검증할 것인가? 재고 변경 시 할 것인가?
코드 느끼기
-
금액 데이터는 클라이언트가 요청을 보내는 것이 아니라, 서버에서 DB 를 통해 조회 하여 조작해야 한다.
- 클라이언트에 의해 변조되면 안되기 때문
-
Kotlin 을 사용할 때 nullable 을 사용하게 되면 객체를 사용하는 입장에서 null 에 대한 검증을 해야 하기 때문에, null 을 포함하지 않는 클래스를 별도로 구성하는 것을 선호한다.
- 파일이 많아지고, 재사용성이 줄어드는 것이 트레이드 오프이다.
-
상품을 구매하는 경로는 2개지만, 상품 구매 로직은 1개에서 사용한다.
- 상품 상세에서 결제하기
- 장바구니에서 결제하기
-
주문 ID 는 외부에 공개하는 것은 주문에 대한 유추가 가능하고 보안상 문제로 주문 키의 개념을 도입해서 난수를 직접 생성하여 사용한다.
-
주문 요청이 들어온 순간 부터 주문 데이터를 먼저 생성하는 것이 데이터를 관리하는 측면에서 중요하다.
- 주문 요청 후 결제로 이루어지지 않으면 더티 데이터가 생성 되는 트레이드 오프가 있지만, 정합성 측면에서는 이점이 있다.
개념도

개념도를 그릴 때는 코드와 1대1로 매칭 될 필요 없이, 우리 현실 세계에서 고민할 법한 내용들을 기준으로 연관관계를 그려내어 단순함을 만들어내도록 유도하는 것이다.
내가 생각하는 개념도 카트는 상품을 담기 위한 보관함일 뿐, 결제는 상품에 의해 이뤄진다. 그렇다면 카트는 도구일 뿐 더 중요하게 여겨질 것은 “상품” 이고 “상품”의 바코드를 찍어 총 결제 금액을 계산하기 때문에, 현재 개념도에 나와있듯 카트보다 상품을 의존하는 것이 올바르다.
결제
코드 느끼기
서비스 로직에서 비즈니스 코드를 작성하는 것 보다 컴포터는화 하여 호출하는 것을 더 선호한다.
PG 사 승인 외부 API 를 거치고 난 이후는 사용자가 실제 잔고에서 돈이 빠져 나갔기 때문에 예외가 던져지는 일이 없어야한다.
개념도

취소
코드 느끼기
PaymentEntity 에서 status 를 관리할 때, READY, SUCCESS 단 두가지의 상태만 갖고 있는다. 취소는 PaymentEntity 의 상태를 업데이트 하지 않고 CancelEntity 에서 관리한다.
이 구조의 장점은 결제와 취소가 분리 되어 있어, DB 테이블에서 각 책임에 맞게 데이터를 “삽입” 만 하게 된다.
데이터를 수정하게 되면, 과거 데이터를 불러 왔을 때도 수정일시를 확인해야 하는 번거로움이 있지만, 삽입된 데이터만 있다면 삽입일시 기준으로 데이터를 조회할 수 있다.
또한, 이미 결제한 건이 30일이 지나서 취소가 되었다고 한다면 이 데이터는 30일 전 데이터를 조회해서 취소 건으로 바꿔야한다. 그럴 경우, Payment 테이블에 updated_at 을 지속적으로 모니터링 해야 하는 단점이 존재한다.
만약 Cacnel 테이블에서 관리 했더라면 30일 후에 취소한 건이 가장 최근에 쌓이게 되고, 이 취소 데이터를 기준으로 정산을 하거나 유저에게 노출을 시키거나 할 수 있게 되는 것이다.
그리고, 정산을 하기 위해서 이 테이블 분리에 장점이 있고, 그 장점이 무엇인지는 정산을 하면서 다룬다.
정산
요구사항 느끼기
이 요구사항을 정리 해보자면?
- 결제한 금액에 대해 정산을 한다.
명확히 기재 해야 할 요구사항은 무엇인가?
- 어떤 데이터를 기준으로 정산을 할 것인가?
- 정산 금액에 대한 지급 대행은 언제 할 것인가?
기술적 고민은 무엇인가?
- 주문에 대한 데이터는 언제 생성할 것인가? 결제 전 / 결제 시
- 재고 변경 후 유효성 검증은 결제 후 검증할 것인가? 재고 변경 시 할 것인가?
코드 느끼기
정산 원천 데이터를 다룰 때, 정산 가공 데이터를 만들기 위한 중간 과정을 한 번 더 두면 추후에 부분 취소를 하거나 다른 요구사항이 생겼을 때 정산 데이터는 깔끔하게 유지하며 요구사항을 반영할 수 있다.
정산 배치를 다룰 때 크론 주기의 시간 기준으로 배치를 수행 시키게 되면, 추후에 데이터가 많아졌을 때 의존하고 있는 크론 잡이 다음 크론잡을 실행 하는 시간이 될 때 까지도 완료되지 않을 경우 정상적으로 동작하지 않을 수 있다.
- 이럴 땐 스텝 기반으로 동작하는 스프링 배치를 활용해볼 수 있다.

전체 개념도
1안: 중요한 개념 위주의 개념도를 그리는데 유리

2안: 참조 관계를 가독성 있게 표현하기 위한 개념도

3안: 복잡한 시스템의 개념도를 그리는 방법
