데이터 베이스/설계 1편, 현대적 데이터 모델링 완전 정복

Ch08. 논리적 모델링 - ERD 작성

webmaster 2026. 6. 28. 15:57

최종 논리적 ERD

논리적 모델링 ERD

한글과 영문 표기 시점

개념적 모델링 단계는 모든 이해관계자가 쉽게 이해할 수 있는 용어를 사용하는 것이 좋다. 따라서 한글을 사용하는 것이 좋다.
물리적 모델링 단계는 실제 데이터베이스 테이블로 만들어야 하기 때문에 반드시 영문으로 표기해야 한다. 그 중간에 있는 논리적 모델링 단계는 한글을 사용하거나 또는 한글과 영문을 병기해서 함께 표기하기도 한다. 논리적 모델링 단계에서도 한글을 사용하는 이유는 실무에서는 물리적 모델링과 논리적 모델링을 함께 진행하는 경우가 많은데, 이 경우 기획자를 포함한 이해관계자들이 모델을 쉽게 읽을 수 있어야 하기 때문이다.

 

1단계: 핵심 엔티티 테이블 설계

가장 기본이 되는 회원(member) 상품(product) 테이블부터 설계한다. 이들은 다른 테이블의 부모가 되는 경우가 많으므로 먼저 정의하는 것이 좋다.

  • 회원 테이블
    • 테이블명: 회원
    • 기본 키(PK): 회원id(대리 키)
    • 컬럼 정의
      • 회원 id: "BIGINT", "PK". 시스템이 자동으로 생성하는 고유 번호.
      • 로그인 id: "VARCHAR(50)". 사용자가 로그인 시 사용하는 아이디. (UNIQUE 제약조건)
      • 비밀번호: "VARCHAR(255)", 해시화하여 저장될 비밀번호.
      • 회원명: "VARCHAR(50)", 회원명.
      • 이메일: "VARCHAR(100)", 이메일 주소. (UNIQUE 제약조건)
      • 주소: "VARCHAR(255)", 기본 배송지 주소.
    • 설계 노트
      • "로그인 id"와 "이메일"은 비즈니스적으로 유일해야 하는 자연 키 후보들이다. 우리는 이들에 UNIQUE 제약조건을 걸어 데이터베이스 레벨에서 중복을 원천적으로 방지한다.
      • 기본 키는 "회원 id"라는 비즈니스와 무관한 대리 키를 사용한다. 덕분에 나중에 회원이 "로그인 id"나 "이메일"을 변경하더라도, 이 회원을 참조하는 수많은 주문(orders) 데이터는 아무런 영향을 받지 않고 안정적으로 유지된다.
  • 상품 테이블
    • 테이블명: 상품
    • 기본 키(PK): 상품 id(대리 키)
    • 컬럼 정의
      • 상품 id: "BIGINT", "PK". 시스템이 자동으로 생성하는 고유 번호.
      • 상품명: "VARCHAR(100)", 상품명.
      • 상품 가격: "INT", 상품 가격.
      • 재고 수량: "INT", 재고 수량.

2단계: 관계를 포함한 테이블 설계 (1:N, M:N)

  • 주문 테이블
    • 테이블명: 주문
    • 기본 키(PK): 주문 id(대리 키)
    • 외래 키(FK): 회원 id
    • 컬럼 정의
      • 주문 id: "BIGINT", "PK". 시스템이 자동으로 생성하는 고유 번호.
      • 회원 id: "BIGINT", "FK". member(회원 id)를 참조. 어떤 회원이 주문했는지 알려준다.(NOT NULL)
      • 주문 상태: "VARCHAR(20)", 주문 상태 (예: ORDERED, SHIPPED, COMPLETED, CANCELED).
      • 주문 일시: "DATETIME", 주문한 날짜 시간.
    • 설계 노트
      • "회원 id" 컬럼은 member 테이블의 "회원 id"를 참조하는 외래 키다.
      • 이 관계는 비식별 관계다. 왜냐하면 orders 테이블은 "회원 id"를 기본 키의 일부로 사용하지 않고, 자신만의 독립적인 기본 키 "주문 id"를 가지고 있기 때문이다.
      • 주문은 반드시 특정 회원이 해야 하므로, "회원 id"에 NOT NULL 제약조건을 추가하여 회원 없는 유령 주문이 생기지 않도록 강제한다(필수 참여)
  • 주문 항목 테이블
    • 테이블명: 주문 항목
    • 기본 키(PK): 주문 항목 id(대리 키)
    • 외래 키(FK): 주문 id, 상품 id
    • 컬럼 정의
      • 주문 항목 id: "BIGINT", "PK". 시스템이 자동으로 생성하는 고유 번호.
      • 주문 id: "BIGINT", "FK". orders(주문 id)를 참조. (UNIQUE1 제약조건), (NOT NULL)=
      • 상품 id: "BIGINT", "FK". product(상품 id)를 참조. (UNIQUE1 제약조건), (NOT NULL)
      • 주문 가격: "INT", 주문 당시의 상품 가격 (스냅샷).
      • 주문 수량: "INT", 주문한 수량.
    • 설계 노트
      • 이 테이블의 설계는 현대적 설계의 핵심을 보여준다. "(주문 id, 상품 id)" 복합키를 기본 키로 사용하는 식별 관계 대신, 별도의 대리 키 "주문 항목 id"를 기본 키로 선택했다.
      • 주문 및 상품과의 관계는 모두 비식별 관계가 되어 모델이 단순해지고, ORM 프레임워크와의 호환성도 극대화된다.
      • "하나의 주문에 동일 상품은 중복될 수 없다"는 비즈니스 규칙은 UNIQUE KEY(주문 id, 상품 id) 제약조건을 추가하여 보장할 것이다.
        • 두 컬럼에 UNIQUE1이라고 표현해서 두 컬럼이 같은 제약조건에 묶이는 것을 표현했다.
      • 주문 가격은 상품 테이블의 현재 가격을 참조하지 않고, 주문이 일어난 시점의 가격을 복사해서 저장한다.
        • 덕분에 나중에 상품 가격이 인상되어도 과거 주문 내역의 정확성은 절대 변하지 않는다.

3단계: 1:1 관계 테이블 설계

  • 배송 테이블
    • 테이블명: 배송
    • 기본 키(PK): 배송 id(대리 키)
    • 외래 키(FK): 주문 id
    • 컬럼 정의
      • 배송 id: "BIGINT", "PK".
      • 주문 id: "BIGINT", "FK".  orders(주문 id)를 참조. (UNIQUE 제약조건), (NOT NULL)
      • 배송 상태: "VARCHAR(20)", 배송 상태 (예: READY, SHIPPING, COMPLETED).
      • 배송지 주소: "VARCHAR(255)", 배송지 주소.
      • 운송장 번호: "VARCHAR(50)", 운송장 번호.
    • 설계 노트
      • 1:1 관계를 구현하는 가장 좋은 방법은 보조 테이블(배송)이  테이블(주문)을 참조하도록 외래 키를 두는 것이다.
      • 주문 id(FK) UNIQUE 제약 조건 추가하는 것이 핵심이다. 제약조건이 배송 테이블에 동일한 "주문 id" 이상 들어오는 것을 막아주어 완벽한 "1:1 관계"를 데이터베이스 레벨에서 보장한다.
  • 결제 테이블
    • 테이블명: 결제
    • 기본 키(PK): 결제 id(대리 키)
    • 외래 키(FK): 주문 id
    • 컬럼 정의
      • 결제 id: "BIGINT", "PK".
      • 주문 id: "BIGINT", "FK". orders(주문 id)를 참조. (UNIQUE 제약조건), (NOT NULL)
      • 결제 수단: "VARCHAR(50)", 결제 수단 (예: CREDIT_CARD).
      • 결제 금액: "INT", 결제 금액.
      • 결제 상태: "VARCHAR(20)", 결제 상태 (예: PAID, FAILED).
      • 결제 일시: "DATETIME", 결제 일시. 은행, 신용카드 회사의 결제 시간을 저장한다.

실무 이야기: 모델의 단순화와 트레이드 오프

지금까지 설계한 모델은 "주문", "배송", "결제"의 역할을 명확하게 분리한, 좋은 설계다. 각자의 책임이 명확하여 향후 기능 확장에 매우 유리하다. 하지만 우리 프로젝트는 이제 시작하는 스타트업의 MVP(최소 기능 제품) 버전이다. 초기 단계에서는 배송과 결제 시스템이 복잡하지 않을 있다. 이런 경우, 실무에서는 빠른 개발 속도와 단순성을 위해 일부 테이블을 합치는 전략적인 선택을 하기도 한다.

 

예를 들어, "배송" 핵심 정보인 "배송지 주소" "배송_상태" 주문 테이블에 직접 포함시켜 배송 테이블을 생략할 수 있다. 이렇게 하면 JOIN없이 주문 테이블만 조회하여 주문과 배송 정보를 번에 있다는 장점이 있다. 하지만 이는 정규화 희생하여 단순성 얻는 트레이드 오프 관계다. 서비스가 성장하여 배송 로직이 복잡해지면, 결국 주문 테이블에서 배송 관련 컬럼들을 다시 분리해야 하는 날이 있다.

 

우리는 일단 원칙에 충실한 설계를 배우고, 상황에 따라 이런 트레이드 오프를 고려할 있는 시야를 갖추는 중요하다.