📝 실전 요구 사항 분석

오프라인에서 대박을 친 스타트업의 온라인 쇼핑몰을 개발하는 일에 투입됐다고 가정해보자. 당연히 요구 사항은 어마어마하게 많을 것이다. 중요한 것은 모든 요구 사항을 일정 안에 한 번에 반영하는 것이 아니라, 지금 서비스에 당장 필요한 기능이 무엇인지에 대해 집중해야 한다. 단순히 기술 구현에만 매몰되지 않고, 비즈니스의 성공이라는 큰 그림을 봐야 한다.

이렇게 핵심 요구 사항을 정의하는 과정을 MVP(Minimum Viable Product)를 정의한다고 한다. 논의 끝에 아래와 같은 1차 오픈 스펙이 정해졌다고 가정해보자.

  • 회원: 고객이 가입하고 자신의 정보를 관리할 수 있어야 한다.
  • 상품: 우리 서비스가 판매할 상품을 등록하고 관리할 수 있어야 한다.
  • 주문: 회원이 상품을 구매할 수 있어야 한다.
  • 결제: 주문에 대한 결제 정보를 기록하고 관리할 수 있어야 한다.
  • 배송: 결제가 완료된 주문의 배송 상태를 관리할 수 있어야 한다.

 

🔍 핵심 엔티티 도출

이제 위 기능 명세서에서 핵심 엔티티를 도출해야 한다. 엔티티(Entity)는 저장할 만한 가치를 지닌 정보를 여러 개 가지고 있으면서, 다른 것과는 명확히 구분되는 유무형의 모든 것이다. 특정 대상이 엔티티인지 아닌지 판단하기 위해서는 해당 대상에 “우리 시스템이 저장하고 관리해야 할 대상인가?” 라고 질문을 던져보자. 보통 명사를 뽑아내면 된다.

  • 회원(Member): 서비스를 사용하는 고객
  • 상품(Product): 판매의 대상이 되는 물건
  • 주문(Order): 회원의 구매 활동 결과
  • 결제(Payment): 주문에 대한 지불 정보
  • 배송(Delivery): 주문된 상품의 물리적 이동에 대한 정보

여기서 “주문” 과 “배송” 을 별도의 엔티티로 분리한 것에 주목해야 한다. “주문” 은 “결제” 까지 포함하는 비즈니스 트랜잭션의 단위이고, “배송” 은 “물류” 라는 별개의 프로세스다. 이렇게 역할을 분리하면 나중에 “주문은 완료되었지만 배송은 시작 전” 과 같은 다양한 상태를 명확하게 관리할 수 있고, 각자의 책임이 분명해져 시스템을 유지보수하기 쉬워진다.

 

⛓️ 속성과 관계 설정

이제 각 엔티티가 어떤 구체적인 데이터 항목들을 가져야 하는지, 엔티티 사이에 어떤 관계로 연결되어 있는지를 요구 사항을 기반으로 정의해야 한다.

  • 회원(Member): 회원 id, 로그인 id, 비밀번호, 회원명, 이메일, 주소
  • 상품(Product): 상품 id, 상품명, 상품 가격, 재고 수량
  • 주문(Order): 주문 id, 주문 상태, 배송지 주소, 주문 일시
  • 결제(Payment): 결제 id, 결제 수단, 결제 금액, 결제 상태, 결제 일시
  • 배송(Delivery): 배송 id, 배송 상태, 운송장 번호
  • 회원 & 주문: 한 명의 회원은 주문을 여러 번 할 수 있다. (1 : N 관계)
  • 주문 & 결제: 하나의 주문은 하나의 결제로 이루어진다. (1 : 1 관계)
  • 주문 & 배송: 하나의 주문은 하나의 배송으로 이루어진다. (1 : 1 관계)
  • 주문 & 상품: 하나의 주문은 여러 개의 상품으로 이루어질 수 있고, 상품도 여러 주문에 속해 있을 수 있다. (M : N 관계)

 

🤔 연관 엔티티로 다대다 관계 해소

여기서 눈에 띄는 관계가 있는데, 바로 주문과 상품이 다대다 관계로 연결되어 있다는 점이다. 상황에 따라 이렇게 다대다 관계로 연결되어 있는 엔티티들은 몇 가지 문제를 일으킬 수 있다.

🚫 문제 1: 물리적으로 구현할 수 없다.

“하나의 주문에는 여러 상품이 포함될 수 있고, 하나의 상품은 여러 주문에 포함될 수 있다.”

말만 들어보면, 아주 타당하다. 하지만 이걸 실제 데이터베이스의 테이블 구조로 옮길 수 없다. 관계형 DB는 테이블이라는 2차원 표 형태를 사용하며, 관계는 한 테이블의 기본 키(Primary Key)를 다른 테이블의 외래 키(Foreign Key)로 포함시켜 표현한다.

이 방식을 다대다 관계에 그대로 적용해보도록 하자. 네이트(회원 id: 1)가 주문 1001번으로 상품 101(키보드)과 상품 102(마우스)를 주문했다고 가정해보자.

시도 1: 주문 테이블에 상품 id 컬럼 추가하기

주문 id (PK)회원 id주문 일시상품 id (FK?)
100112026-09-17101
???12026-09-17102

보다시피 주문 id가 1001인 주문에 마우스를 담을 수가 없다. 주문 id를 1002로 해서 새로운 주문을 생성해버리면 그건 더 이상 같은 주문이 아니게 되므로 의미가 없다. 이 구조는 하나의 주문에 단 하나의 상품만 가질 수 있기 때문에 여러 상품을 주문한 경우를 표현할 수 없다.

시도 2: 컬럼 계속 추가하기

주문 id (PK)회원 id주문 일시상품1 id상품2 id상품3 id
100112026-09-15101102NULL
100232026-09-16103NULLNULL
100312026-09-17102103104

대충 봐도 상황이 더 심각해졌다. 만약 어떤 고객이 상품을 4개 이상 주문한다면 개발자가 일일이 DDL을 실행해야 한다. 그리고 대부분의 주문에 어마어마하게 많은 상품들이 포함되지는 않을 텐데, 컬럼을 미리 많이 만들어두면 저장 공간이 상당 부분 낭비될 것이다. 마지막으로 특정 상품이 포함된 주문들을 모두 찾고 싶은 경우에는 WHERE 절에 모든 상품 id 컬럼을 작성해야 한다.

시도 3: 한 컬럼에 여러 값을 저장하도록 처리하기

주문 id (PK)회원 id주문 일시상품 ids
100112026-09-15“101, 102”
100222026-09-17“101, 102, 103”

결국 최악의 실수를 저질러버리고 말았다. 이 방식은 관계형 DB의 근간을 흔드는 최악의 설계다.

만약 마우스가 포함된 모든 주문을 찾기 위해서는 매번 복잡하고 비효율적인 텍스트 검색을 해야 하고, 데이터를 수정하거나 삭제해야 할 경우에는 문자열을 긁어와서 파싱한 후에 다시 저장해야 한다. 그리고 무엇보다 이 설계는 데이터베이스의 제 1정규형, “테이블의 모든 칸은 원자적인 값 하나만 가져야 한다” 는 대원칙을 위반한다.

 

🚫 문제 2: 관계에 속한 데이터를 저장할 장소가 없다.

주문과 상품 엔티티의 관계를 잘 들여다보면, 주문이 발생할 그 순간에만 의미를 갖는 데이터들이 존재한다. 현재 엔티티들로는 이 데이터들을 저장할 장소가 없다는 것이 문제다. 주문이 발생한 이후에 상품 정보가 일부 수정되었다고 가정해보자.

<주문 전 상품 테이블>

상품 id (PK)상품명가격(원)재고 수량(개)
101기계식 키보드120,00050
102무소음 마우스45,000100

<주문 후 상품 테이블>

상품 id (PK)상품명가격(원)재고 수량(개)
101기계식 키보드150,00020
102무소음 마우스40,000120

만약 고객이 기계식 키보드와 무소음 마우스를 각각 1개씩 구매했는데, 주문 이후에 가격이 변경되었다면 주문 당시의 주문 수량과 주문 가격은 어디에 저장해야 할까? 주문 테이블? 상품 테이블? 근데 생각해보면, 주문 테이블에는 변경 이전과 이후의 가격 중 어떤 가격을 컬럼에 넣어야 할지도 모르고, 상품 테이블에 넣자니 만약 상품이 없어졌다면…? 둘 다 말이 안 된다. 이렇게 주문 수량이나 주문 당시 가격은 주문이나 상품에 속한 속성이 아니라는 느낌이 강하게 온다. 이들은 주문과 상품이 관계를 맺음으로써 발생하는 속성이다.

 

👍 해결책: 관계를 엔티티로 승격하기

해결책은 다대다 관계를 연관 엔티티(Associative Entity)로 바꾸는 것이다. 두 엔티티의 관계를 나타내는 새로운 엔티티를 만들어, 기존의 다대다 관계를 2개의 일대다 관계로 풀어내는 방식이다.

여기서 주문 항목(order_item)이라는 새로운 연관 엔티티를 만드는 것이다.

이런 식으로 다대다 관계를 일대다(주문 & 주문 항목), 다대일(주문 항목 & 상품) 관계로 풀어내면 아래와 같이 깔끔하게 설명이 가능하다.

  • 하나의 주문에는 최소 하나 이상의 주문 항목이 포함, 하나의 주문 항목은 특정한 주문 한 건에 포함된다.
  • 하나의 주문 항목은 반드시 하나의 상품을 가져야 하고, 하나의 상품은 여러 번 주문될 수도, 아직 한 번도 주문되지 않을 수도 있다.

이처럼 다대다 관계를 발견하면 단순히 연관 엔티티를 설계하는 것을 넘어, 해당 관계가 만들어지는 순간에만 의미를 갖는 데이터는 무엇인지를 적극적으로 질문하고 찾아내야 한다.

 

🖌️ ERD 작성

지금까지 분석한 MVP 기능을 바탕으로 개념적 모델 ERD를 그려보면 아래와 같다.

📖 용어 사전 작성

프로젝트의 모든 이해 관계자들은 모두 같은 비즈니스 용어를 사용하는 것이 중요하다. 그렇지 않으면 시스템 구조를 파악하는 데 엄청난 시간과 비용이 발생하고, 유지보수하는 것도 힘들다. 이런 문제를 방지하기 위해 프로젝트 초기에 모든 구성원이 합의하여 비즈니스 용어와 실제 물리적인 데이터 이름을 매핑해두면, 모두가 일관된 용어를 사용하여 소통하고 개발할 수 있다. 이는 사소해 보이지만, 장기적으로는 개발 생산성과 시스템의 안정성을 극대화하는 매우 중요한 활동이다.

용어를 역할별로 분류하고, 약어와 전체 영문명을 명확히 구분하며, 실제 시스템에서 어떻게 사용되는지 구체적인 예시를 함께 기록하는 것이 좋다. 회원(member), 가격(price)처럼 가장 작은 단위의 단일어를 중심으로 정의하고, 이를 조합해서 작성해보도록 하자.

분류명칭전체 영문명축약어설명관련 시스템 요소
엔티티회원member서비스를 사용하는 고객member 테이블
상품product판매하는 물건 또는 서비스product 테이블
주문order회원의 상품 구매 요청 행위orders 테이블
주문 항목order_item하나의 주문에 포함된 개별 상품 정보 (연관 엔티티)order_item 테이블
결제paymentpay주문에 대한 지불 정보payment 테이블
배송delivery주문된 상품의 물리적 이동 정보delivery 테이블
주요 속성식별자identifierid데이터를 고유하게 식별하는 번호member_id, product_id
이름, 명name사람, 상품 등 대상을 지칭하는 명칭member_name, product_name
가격price상품의 현재 판매가 또는 주문 시점의 가격order_price
금액amount금액
재고stock상품의 재고stock_quantity
수량quantity개수나 양stock_quantity, order_quantity
개수, 횟수count개별 항목을 하나씩 세는 행위, 이벤트 발생 횟수visit_count, click_count, employee_count
상태status주문, 배송 등 엔티티의 현재 상태를 나타내는 값order_status, delivery_status
주소addressaddr위치 정보member.addr, order.ship_addr
비밀번호password로그인 시 사용하는 비밀번호member.password
배송shippingship배송과 관련된 속성을 나타낼 때 접두사로 사용ship_addr
로그인login시스템에 접속하는 행위member.login_id
수단method결제 수단payment_method
번호numberno번호tracking_no
운송장 번호tracking_no배송 추적 번호
행위 / 수식어생성create데이터가 만들어진 시점을 나타낼 때 사용created_at
수정update데이터가 변경된 시점을 나타낼 때 사용updated_at

용어 사전은 한 번 만들고 끝나는 문서가 아니다. 프로젝트가 진행되면서 새로운 용어가 추가되고 새로운 비즈니스 용어가 생겨날 때마다 지속적으로 업데이트되어야 한다.

0개의 댓글