최근 쇼핑몰 프로젝트 공부를 하면서 쇼핑몰 ERD 설계를 진행했습니다.
1차적으로 ERD 설계를 하고, 프로젝트를 진행하면서 엔티티를 분리해야 할 것은 분리하고
불필요하다 생각되는 것들은 삭제했지만
프로젝트를 마치고 나니 ERD 설계에 대해서 부족한 점들이나 회고할 부분들이 있어
포스팅을 하게 되었습니다.
전체 다이어그램 입니다.
지금 생각해보면, 굳이 저렇게 설계할 필요가 있었을까? 하는 부분들이 있습니다.
예를들어 처음부터 재고 ERP 를 생각 하고 , ERD 를 설계 했다면 어땠을까?
초기 단계 이기 때문에, 완벽하게 하려기 보다는 확장이 편리하도록 설계하는 것은 어땠을까?
회원/프로필/마이페이지/편의성 등을 고려해서 각 엔티티의 핵심 기능을 기준으로 속성값을 최소한으로 줄여본다면 어떻게 하면 될까?
등등 생각해볼 수 있었을 텐데, 아쉬운 점이 있습니다.
현재의 쇼핑몰 ERD 가 많이 부족하지만, 어떤 의도와 목적으로 엔티티를 설계 했으며
각 엔티티에 대해서 이유, 아쉽다면 아쉬운 점을 설명드리며 회고하며 설명 드리겠습니다.
회원과 회원정보(프로필) 엔터티
최근 SQLD 자격증을 취득하게 되었는데, "데이터 모델링"에 대한 부분을 알게되었습니다.
엔터티는 사전적인 의미로 '독립체'라고 하는데요, "업무에서 쓰이는 데이터를 용도별로 분류한 그룹"이라고 할 수 있습니다.
여기서 용도라는 점에 키 포인트를 잡아야 합니다.
여기서 엔터티에 대한 성질을 적용한 것이 두 엔터티 입니다.
Member 는 회원가입/로그인에 대한 인증 및 인가 부분에 대한 서비스를 담당하는 엔티티 입니다. 회원 프로필 Profile 은 마이 페이지에서 회원의 결제내역이나 회원 정보, 나의 배송지 목록, 추후 고려할 사항인 쿠폰이나 마일리지와 같은 것들도 이곳에서 확인할 수 있습니다.
그런데 Member 엔터티에 Profile 엔터티를 분리하지 않고, Member 엔터티가
인증 및 회원 정보에 대한 역할을 모두 담당하게 된다면 어떻게 될까요 ??
인증 서비스 / 마이 페이지 서비스는 Member 엔터티에 대해서 의존성을 가지게 됩니다.
Member 엔터티에 대한 코드가 변하면 인증 서비스 / 마이 페이지 서비스도 영향을 받게 됩니다.
결국 서로서로 엮이고 엮이게 되어서, 유지 보수가 어려워 질 것입니다.
하지만 Member 엔터티와 Profile 엔터티를 분리함 으로,
인증에 대한 역할은 Member 엔터티가 담당하게 되고,
회원 정보 및 회원 전용 서비스에 대한 부분은 Profile 엔터티가 담당하게 됩니다.
그러므로 추후 Profile 엔터티에 대해서 회원 서비스를 확장할 수 있는 것입니다.
(쿠폰이나 마일리지와 같이 회원들이 필요로 하는 서비스 및 정책 확장)
또한 엔터티는 핵심이 되는 서비스를 1개씩 담당 하고 있기 때문에
디버깅에도 유리합니다. Member 엔터티에 대해서 서비스단, 컨트롤러단, 레퍼지토리단 등
어디서 문제가 발생했는지 직관적으로 발견할 수 있습니다.
장바구니, 주문, 결제 엔터티
Orders 와 PaymentHistory 에 대한 이해
주문 엔터티와 결제내역 엔터티는 다른 기능을 담당합니다. 쇼핑몰에서 주문을 진행할 때, 아임포트와 같은 결제 대행사에서 merchantUid 와 같은 가맹점 주문 번호를 받아 저장합니다. 실제로 회원이 주문할 때 입력한 배송지나, 결제 여부(주문 창에서 결제를 취소할 경우도 있음) 등 주문 시에 필요한 속성들을 받아줍니다. 주문은 결제가 완료되었다는 의미에서의 절차를 말하는 것이 아닙니다. 아직 주문이 정상적으로 진행이 되었는지 알 수 없습니다. 사용자는 결제를 진행하다가, 갑자기 인터넷 브라우저를 종료시켜서 주문을 취소할 수도 있습니다. 주문의 모든 단계가 정상적으로 진행이 되어 결제가 완료 되었을 때(카드사 승인, api 서버에서의 정상적인 서버응답 등등) 결제 내역이 생성됩니다.
결제내역 엔터티는 주문에 대한 전체적인 프로세스가 처리된 이후에 생성됩니다. 저희는 결제내역을 각 상품에 대해서 생성하도록 설계하였습니다. 위 사진에 있는 다양한 속성들이 존재하며, 결제 내역을 회원이 확인할 수 있습니다.
엔터티는 각 상품에 대한 결제내역이 따로 존재하도록 설계 했지만,
주문할때에 상품 A,B,C,D 를 한꺼번에 결제 했다면 order_id 는 모두일치하기 때문에
클라이언트에서는 order_id 를 기준으로 그룹화 하여 결제 내역을 확인할 수 있도록 하였습니다.
장바구니에 있는 내용을 시작점으로 주문이 진행되며, 결제가 완료되면 장바구니에 있는 상품들이 초기화 됩니다.
클라이언트 단에서 그룹화하는 함수의 소스코드 입니다.
// Helper function to group payments by orderId
export const groupByOrderId = (paymentHistory) => {
return paymentHistory.reduce((groups, payment) => {
const {
orderId,
paymentId,
memberId,
buyerAddr,
discount,
merchantUid,
ordererName,
orderedAt,
paiedAt,
payMethod,
phoneNumber,
review,
statusType,
totalPrice,
imagePath,
productName,
productPrice,
productQuantity,
option,
productId,
manufacturer,
} = payment;
if (!groups[orderId]) {
groups[orderId] = {
orderId,
memberId,
buyerAddr,
discount,
merchantUid,
ordererName,
orderedAt,
paiedAt,
payMethod,
phoneNumber,
statusType,
totalPrice,
products: [],
};
}
groups[orderId].products.push({
imagePath,
productName,
productPrice,
manufacturer,
productQuantity,
option,
productId,
paymentId,
review,
});
return groups;
}, {});
};
상품& 상품 썸네일 & 찜리스트 엔티티
상품
상품은 말 그대로 상품에 대한 등록(색상, 사이즈 등은 정하지 않은 상태의 상품 자체)을 말합니다. 예를 들어 T 셔츠가 하나 존재한다고 할지라도, 색상 및 사이즈가 다양할 것입니다. 그 다양한 색상과 사이즈에 대해서 상품 하나하나를 만드는 것은 비효율적입니다.
라운드 브이넥 < 이라는 이름을 가진 상품이 존재한다면, 그 상품에 대해서 ProductManagement 에서는 다양한 색상과 사이즈 등등 옵션을 부여하여 각 상품에 대한 옵션에 대해서 수량과 조건들을 가지도록 엔티티를 설계한 것입니다.
상품 썸네일 이미지 엔티티 (ProductThumbnail)
상품 이미지를 상품 엔티티 내의 속성으로 가져가도 되겠습니다. 하지만 상품 썸네일 이미지의 개수가 상품마다 다르며, 상품의 썸네일 이미지는 상품 상세페이지 및 카테고리 페이지에서 굉장히 중요한 엔티티이기 때문에 별도의 엔티티를 정의하였습니다. 만약 속성으로 정의했다면, Thumbnail1, Thumbnail2, Thumbnail3 와 같이 속성값들을 각각 정의하거나 ImagePath 를 List 로 받아서, ArrayList 형식으로 추가될때마다 추가해도 됐습니다. 하지만 이 썸네일 이미지를 조회할때마다 Product 엔티티를 조회해야하며, 불필요한 데이터까지도 조회해야 한다는 점이 아쉬울 것입니다.
프로젝트를 진행하며, 카테고리와 상품 옵션에 대한 필터링과 상품 전체 조회에 대한 페이지네이션을 스프링 MVC와 QueryDSL로 구현했는데
이때 상품 이미지 엔티티(ManyToOne 상품)에 대해 Fetch Type 을 Lazy 로 해주지않아 즉시로딩 되었고
N+1 문제가 발생했습니다. 그래서 Lazy 로 적용하고 상품 썸네일 이미지는 Join Fetch 를 이용해서 한 테이블에 함께 조회했습니다.
찜한상품 엔티티
찜한 상품에 대해 알려주는 엔티티입니다. 상품의 id 와 찜 여부를 알려줍니다.
상품 재고 및 물류이동(재고/출고)
상품을 따로 생성하고, 그 후에 상품에 대한 옵션들과 수량을 관리하는 ProductManagement 엔터티를 정의 했습니다.
ProductManagement 에서는 상품 카테고리나, 상품 정보, 사이즈, 수량 관리를 담당하고 있습니다.
상품 재고 InventoryProduct 는 상품 재고의 수량을 담당하고 있습니다. 사실 ProductManagement 에서 수량 관리는 담당하지 않고 있어서 삭제 예정입니다. 상품 재고 InventoryProduct 엔터티를 따로 빼낸 이유는, 상품 재고 수량에 대한 재고 시스템 ERP 를 따로 두는 것이 굉장히 자주 이용되는 서비스인 재고 서비스에 유리하기 때문입니다.
전사적 자원 관리(enterprise resource planning) ERP 는 서비스의 복잡도에 따라서 ERD 도 더 복잡해질 수 있다고 생각하지만, 현재 재고 관련 백오피스 개발을 공부 중인 저에게는 충분하다 생각되어서, 입고와 출고 같은 경우 movement Type 카테고리 유형으로 처리해서 같은 테이블 내에서 입/출고를 확인하고 , 클라이언트 단에서는 카테고리 유형에 따라 다른 API 를 호출하도록 필터링을 적용시키면 됩니다.
[아쉬운 점]
처음 ERD 를 설계할 때, 상품 관리에 대한 엔터티(ProductManagement)와 상품 재고에 대한 엔터티(InventoryProduct)를 미리 분리하지 못한 점입니다. 조금 더 길게 생각 하지 못한 제 실수 입니다. 원래는 ProductManagement 를 상품 재고를 관리하는 Management 개념의 엔티티로 정의 했는데, 그걸 미리 백엔드 개발 팀원에게 말해주지 못해서 상품 관리에 대한 서비스단에서 결제 및 취소를 진행할 때에 재고 수량을 조절하도록 구현하지 못했습니다.
그래서 상품 상세 페이지나 카테고리 페이지에서도 재고 엔터티에 대한 API 를 적용하고 설계 했다면, 조금 더 효율적이고 유지보수면에서 간단해졌을 것이라 생각 합니다.
사실 쇼핑몰에서 재고 ERP 가 제일 핵심적인 비즈니스 로직이라고 생각 하는데, 지나고 보니 회계학에 대한 개념이나 ERP 에 대한 기본 지식들을 미리 이해하고 있어야 ERP 를 개발할 때에 어려움이 없었겠구나 라는 생각이 듭니다.
상품 카테고리
상품 카테고리는 상위 카테고리와 하위 카테고리를 계층별로 구현하여, Depth (계층)을 가진 카테고리가 되도록 설계되었습니다.
[
{
"categoryId": 1,
"name": "top",
"depth": 0,
"children": [
{
"categoryId": 2,
"name": "blouse",
"depth": 1,
"children": []
},
{
"categoryId": 3,
"name": "hoodie",
"depth": 1,
"children": []
},
{
"categoryId": 4,
"name": "knit-sweater",
"depth": 1,
"children": []
},
{
"categoryId": 5,
"name": "long-shirts",
"depth": 1,
"children": []
},
{
"categoryId": 6,
"name": "long-sleeve",
"depth": 1,
"children": []
},
{
"categoryId": 7,
"name": "short-shirts",
"depth": 1,
"children": []
},
{
"categoryId": 8,
"name": "short-sleeve",
"depth": 1,
"children": []
},
{
"categoryId": 9,
"name": "sweatshirt",
"depth": 1,
"children": []
}
]
},
{
"categoryId": 10,
"name": "bottom",
"depth": 0,
"children": [
{
"categoryId": 11,
"name": "long",
"depth": 1,
"children": []
},
{
"categoryId": 12,
"name": "shorts",
"depth": 1,
"children": []
},
{
"categoryId": 13,
"name": "skirt",
"depth": 1,
"children": []
}
]
},
{
"categoryId": 14,
"name": "dress&set",
"depth": 0,
"children": [
{
"categoryId": 15,
"name": "dress",
"depth": 1,
"children": []
},
{
"categoryId": 16,
"name": "set-up",
"depth": 1,
"children": []
},
{
"categoryId": 17,
"name": "two-piece",
"depth": 1,
"children": []
}
]
},
{
"categoryId": 18,
"name": "outer",
"depth": 0,
"children": [
{
"categoryId": 19,
"name": "cardigan",
"depth": 1,
"children": []
},
{
"categoryId": 20,
"name": "coat",
"depth": 1,
"children": []
},
{
"categoryId": 21,
"name": "jacket",
"depth": 1,
"children": []
},
{
"categoryId": 22,
"name": "lightweight-padding",
"depth": 1,
"children": []
},
{
"categoryId": 23,
"name": "long-padding",
"depth": 1,
"children": []
},
{
"categoryId": 24,
"name": "mustang",
"depth": 1,
"children": []
},
{
"categoryId": 25,
"name": "short-padding",
"depth": 1,
"children": []
},
{
"categoryId": 26,
"name": "vest",
"depth": 1,
"children": []
}
]
},
{
"categoryId": 27,
"name": "shoes",
"depth": 0,
"children": [
{
"categoryId": 28,
"name": "boots",
"depth": 1,
"children": []
},
{
"categoryId": 29,
"name": "sandal",
"depth": 1,
"children": []
},
{
"categoryId": 30,
"name": "sneakers",
"depth": 1,
"children": []
}
]
},
{
"categoryId": 31,
"name": "accessories",
"depth": 0,
"children": [
{
"categoryId": 32,
"name": "bag",
"depth": 1,
"children": []
},
{
"categoryId": 33,
"name": "cap",
"depth": 1,
"children": []
},
{
"categoryId": 34,
"name": "socks",
"depth": 1,
"children": []
}
]
}
]
그래서 추후 카테고리 종류나 계층이 추가되어도 문제가 없도록 설계했습니다.
카테고리에는 특별한 내용이 없기 때문에 넘어가도록 하겠습니다.
상품 리뷰 엔티티와 상품 문의 엔티티
이번 ERD 설계 시 상품 리뷰와 상품 문의에 대한 엔티티를 분리하도록 회의가 되었습니다.
첫번째 이유는 쇼핑몰에 문의 게시글은 비구매자의 색상/사이즈 등 단순 문의, 배송에 대한 문의, 재입고 등 기타 문의 등등 다양한 문의 글이 존재하며 문의에 대한 다양한 옵션들이 존재하기 때문입니다. (비밀글 여부, 비로그인 사용자도 이용 가능함, 등등)
두번째 이유는 상품 리뷰 게시글 같은 경우는, 주문 처리가 완료되어 결제 완료된 사용자에 대해서만 작성이 가능하고(인가) 리뷰 이미지와 별점시스템이 존재하기 때문입니다.
쇼핑몰에서 문의글과 리뷰글은 특성이 다르고, 쇼핑몰 서비스 로직에 따라서 구체적인 필터링 옵션들이 추가될 수 있지만
이번 프로젝트에서는 이와같이 따로 구별해서 엔티티를 설계 해보았습니다.
느낀점
위와 같이 쇼핑몰 Entity Relationship Diagram 를 설계해보았습니다. 쇼핑몰의 성질을 다양한 서비스가 존재하기 때문에,
각 서비스 로직에 대한 이해도가 가장 중요하며, 저와 같이 백엔드 개발자는 주요 서비스 로직인 결제, 상품, 카테고리, 회원에 대한 서비스에 대해서 조회 성능이나 엔티티 복잡도에 대해서 고려해봐야 할 것 같습니다.
예전 쇼핑몰 ERD 게시글이 많은 인기가 있었는데요, 어떤 블로그에는 출처도 없이 제 ERD 설계를 보시고 .. 거의 똑같이 사용하시는 분도 있었습니다. 그때와 다르게 많은 성장이 있는 ERD 설계 입니다. 아쉬운 부분이 있지만, 이를 바탕으로 더욱 좋은 설계를 진행할 수 있을 것 같습니다.
감사합니다.
'Computer Science > Server' 카테고리의 다른 글
도커 이전에, 도커의 작동원리란? (6) | 2024.07.23 |
---|---|
상품 전체 리스트 API 부하 테스트 결과 분석. [JMeter] (1) | 2024.07.22 |
클라우드와 온프레미스 : 클라우드 컴퓨팅의 구조 (4) | 2024.07.21 |
[Server] 세션 기반 인증 VS 토큰 기반 인증 (0) | 2024.06.13 |