// education

정보 모델링

재고표가 두 장이 되는 순간

책마루 사장님이 온라인 주문 시스템을 만들기로 하면서 가장 먼저 부딪힌 문제는 "재고를 어디에 어떻게 저장할 것인가"였다. 매장 POS에도 재고 수량이 있고, 온라인 주문 시스템에도 재고 수량이 필요하다. 이 둘을 따로 만들면 손님이 온라인으로 책을 주문했는데 매장에서는 이미 팔린 뒤라는 상황이 생긴다.

이 문제는 코드를 짜기 전에, 데이터를 어떻게 구조화할지부터 정리해야 풀린다. "상품이라는 게 뭘로 이루어져 있는가", "주문과 상품은 어떤 관계인가", "한 주문에 상품이 여러 개 들어갈 수 있는가" — 이런 질문에 답하는 작업이 정보 모델링이다. 6장에서 요구사항을 규명하고 7~8장에서 기능과 동작을 모델링했다면, 이번 장은 시스템이 다뤄야 할 데이터의 정적인 구조를 그리는 작업이다.

정보 모델링의 결과물은 코드가 아니라 그림과 표다. 그런데도 이 작업이 중요한 이유는, 데이터 구조를 잘못 설계하면 나중에 기능을 아무리 잘 짜도 "재고가 두 곳에서 따로 논다" 같은 문제가 근본적으로 해결되지 않기 때문이다. 정보 모델링에서 가장 널리 쓰이는 도구가 EER(Enhanced Entity-Relationship) 모델, 흔히 개체-관계도(ERD)라고 부르는 표기법이다.

엔티티와 속성 — 시스템이 기억해야 할 것들

엔티티와 엔티티 타입

**엔티티(entity)**는 독립적으로 존재하며 우리가 정보를 추적해야 할 실세계의 사물이나 개념이다. 책마루 시스템이라면 "손님 김도현", "상품 노트 A4", "주문 20260802-001" 같은 것들이 각각 하나의 엔티티다.

같은 종류의 속성을 공유하는 엔티티들의 집합을 엔티티 타입이라 부른다. "손님 김도현"과 "손님 박서연"은 각각 별개의 엔티티지만, 둘 다 이름·전화번호·주소라는 같은 속성 구조를 공유하므로 "고객"이라는 하나의 엔티티 타입으로 묶인다. 실무에서는 이 구분을 엄밀히 따지지 않고 "엔티티"라는 말로 타입까지 함께 지칭하는 경우가 많다 — 이 장에서도 문맥상 명확할 때는 "엔티티"로 통일해서 쓴다.

엔티티 타입을 찾아내는 가장 실용적인 방법은 요구사항 문서를 읽으며 명사를 표시하는 것이다. "손님이 상품을 장바구니에 담아 주문한다"는 문장에서 손님·상품·장바구니·주문이 모두 엔티티 후보가 된다. 다만 명사라고 전부 엔티티가 되는 것은 아니다 — 그 명사가 스스로 값을 가지면 엔티티가 아니라 속성일 가능성이 높다.

속성과 키

**속성(attribute)**은 엔티티를 기술하는 값이다. "상품" 엔티티 타입이라면 상품명, 가격, 재고수량이 속성이다. 여러 엔티티를 서로 구별할 수 있게 해주는 속성(또는 속성의 조합)을 **키(key)**라 하고, 그중 하나를 대표로 선정한 것이 주 키(primary key), 나머지 후보는 **대체 키(alternate key)**가 된다.

책마루의 상품 엔티티 타입을 표로 정리하면 이렇다.

엔티티 타입 속성
상품 상품번호, 상품명, 카테고리, 가격, 재고수량 상품번호(주 키)
고객 고객번호, 이름, 전화번호, 주소, 회원가입일 고객번호(주 키), 전화번호(대체 키)
주문 주문번호, 주문일시, 배달방법, 주문상태 주문번호(주 키)

속성과 엔티티를 헷갈리지 않는 법: "카테고리"는 상품의 속성일까, 별도 엔티티일까? 카테고리가 단순한 값(예: "문구")이면 속성으로 충분하다. 하지만 카테고리마다 별도의 진열 위치나 담당 직원 정보를 관리해야 한다면 카테고리 자체가 독립적으로 추적해야 할 엔티티가 된다. 이 판단은 요구사항에 달려 있다 — 정답은 하나가 아니고, "이 정보를 독립적으로 추적할 필요가 있는가"가 기준이다.

엔티티인지, 속성인지, 관계인지 갈리는 순간

정보 모델링을 처음 해보면 같은 명사를 두고 "이건 엔티티인가, 속성인가"를 놓고 판단이 흔들리는 경우가 자주 생긴다. 다음 세 가지 질문을 순서대로 던져보면 대부분 정리된다.

  1. 이 명사가 여러 속성을 가지고 있는가? 여러 속성을 가진다면 엔티티일 가능성이 높다. 예를 들어 책마루의 "배달"이 배달원, 배달시각, 배달상태처럼 여러 속성을 가진다면 이는 주문의 속성 하나가 아니라 독립된 엔티티다.
  2. 분석가가 이 정보를 시간이 지나도 추적해야 하는가? "이 손님이 과거에 어떤 상품을 주문했는지"를 계속 추적해야 한다면 주문은 단순한 이력 문자열이 아니라 엔티티로 다뤄야 한다.
  3. 동사로 표현되는 문장이 두 개의 독립적인 엔티티를 잇고 있는가? "고객이 상품을 주문한다"처럼 동사(주문한다)가 두 엔티티(고객, 상품)를 연결한다면 그 동사는 관계 타입의 이름이 된다. 반대로 "고객이 이름을 가진다"처럼 동사가 엔티티와 단순한 값을 잇는다면 그 값은 속성이다.

이 판단 기준은 정답을 자동으로 찾아주는 공식이 아니라, 분석가가 사용자와 대화하며 확인해야 할 질문 목록에 가깝다. 책마루처럼 요구사항이 명확히 문서화되어 있지 않은 소규모 프로젝트일수록, 사장님이나 직원과 직접 이야기하며 "이 정보를 나중에 따로 찾아봐야 하나요?"를 물어보는 과정이 정보 모델링의 실제 작업 대부분을 차지한다.

관계 — 엔티티들을 연결하는 의미

엔티티 하나만으로는 시스템이 만들어지지 않는다. "고객이 주문을 한다", "주문에 상품이 담긴다"처럼 엔티티 사이의 연관성이 있어야 비로소 시스템의 데이터 구조가 완성된다. 이 연관성을 **관계(relationship)**라 하고, 같은 형태의 관계들의 집합을 관계 타입이라 한다. EER 표기법에서 관계 타입은 마름모로 그린다.

매핑 제약조건 — 몇 대 몇으로 연결되는가

관계 타입마다 참여하는 엔티티 수를 제한하는 규칙을 매핑 제약조건이라 하며, 크게 세 가지로 나뉜다.

매핑 수 책마루 예시 의미
일대일(1:1) 주문 — 결제 하나의 주문은 하나의 결제 건과 연결된다
일대다(1:N) 고객 — 주문 한 명의 고객이 여러 건의 주문을 낼 수 있다
다대다(M:N) 주문 — 상품 한 주문에 여러 상품이 담기고, 한 상품은 여러 주문에 나타날 수 있다

다대다 관계는 그 자체로 정보를 담기 어렵다는 특징이 있다. "주문과 상품이 다대다로 연결된다"는 것만으로는 "그 주문에서 그 상품을 몇 개 샀는지"를 표현할 수 없다. 그래서 다대다 관계 타입은 흔히 수량 같은 자기 속성을 갖는다 — 책마루라면 "주문상품"이라는 관계 타입이 수량이라는 속성을 가지고, 나중에 관계형 테이블로 바뀔 때 독자적인 테이블(주문번호, 상품번호, 수량)이 된다.

참여 제약조건과 참조 무결성

참여 제약조건은 한 엔티티가 관계에 반드시 참여해야 하는지(필수), 참여하지 않아도 되는지(선택)를 지정한다. 예를 들어 "고객 — 주문" 관계에서 모든 주문은 반드시 어떤 고객에게 속해야 하지만(필수), 모든 고객이 반드시 주문을 한 건 이상 내야 하는 건 아니다(선택) — 회원가입만 하고 아직 주문하지 않은 고객도 있을 수 있기 때문이다.

**참조 무결성(referential integrity)**은 관계에 참여하는 데이터가 실제로 존재하는 엔티티만 참조하도록 강제하는 제약이다. 주문 테이블의 "고객번호"는 반드시 고객 테이블에 실제로 존재하는 고객번호여야 한다 — 존재하지 않는 고객번호를 가리키는 주문이 생기면 데이터의 일관성이 깨진다. 이 제약은 나중에 관계형 데이터베이스에서 외래 키(foreign key) 제약조건으로 그대로 구현된다.

관계 타입의 차수 — 2차와 3차

관계 타입의 **차수(degree)**는 그 관계에 참여하는 엔티티 타입의 수다. 대부분의 관계는 두 엔티티 사이의 2차 관계지만, 세 엔티티가 동시에 얽혀야만 의미가 성립하는 3차 관계도 있다.

책마루가 여러 택배사와 계약해 배달비를 지역별로 다르게 책정한다고 하자. 택배사, 배송지역, 배송비 사이의 관계는 2차 관계 두 개로 쪼갤 수 없다 — "택배사 A와 배송지역 B의 조합"이 정해져야만 배송비가 정해지기 때문이다. 이런 경우 택배사·배송지역·배송비를 하나의 3차 관계 타입("배송비책정")으로 묶어야 정보를 정확히 표현할 수 있다.

왜 이걸 굳이 3차로 두는가: 억지로 2차 관계 두 개로 쪼개면 "택배사가 어떤 지역을 다루는지"와 "지역마다 배송비가 얼마인지"는 표현되지만, "같은 지역이라도 택배사에 따라 배송비가 다르다"는 사실을 잃어버린다. 3차 관계가 필요한지 판단하는 기준은 항상 "이 정보가 두 엔티티만으로 설명되는가, 세 엔티티가 동시에 있어야 설명되는가"다.

일반화와 특수화 — 공통점을 위로 끌어올리기

책마루의 결제 수단은 카드결제와 계좌이체 두 가지다. 둘 다 "결제번호, 결제금액, 결제상태"라는 공통 속성을 갖지만, 카드결제는 승인번호가, 계좌이체는 입금자명과 입금예정일이 추가로 필요하다. 이 두 엔티티 타입을 완전히 따로 설계하면 공통 속성을 두 번 정의해야 하고, "결제 전체 목록을 조회한다" 같은 기능을 만들 때도 두 테이블을 매번 따로 다뤄야 한다.

**일반화(generalization)**는 이럴 때 여러 엔티티 타입 사이의 공통된 속성을 모아 하나의 상위 엔티티 타입으로 정의하는 기법이다. "결제"라는 상위 클래스를 두고, "카드결제"와 "계좌이체"를 그 하위 클래스로 두면 공통 속성(결제번호, 결제금액, 결제상태)은 상위 클래스에 한 번만 정의된다. 상위 클래스의 모든 속성은 하위 클래스에 상속된다 — 이 관계를 흔히 "is_a" 관계라 부른다. 반대 방향, 즉 하나의 엔티티 타입을 여러 하위 클래스로 나누는 작업은 특수화라 한다.

중복성과 완전성

일반화 구조를 정확히 표현하려면 두 가지를 추가로 정해야 한다.

이 두 조건을 합치면 책마루의 결제 일반화는 "해체, 전체 특수화"가 된다. 반면 고객 엔티티 타입을 회원과 비회원으로 나누는 경우를 생각해보면, 이 역시 한 고객이 회원이면서 동시에 비회원일 수는 없으니 해체이고, 모든 고객은 회원 아니면 비회원이므로 전체 특수화다.

ERD를 실제 테이블로 바꾸기

정보 모델링의 결과인 EER 모델은 그 자체로 시스템이 되지 않는다. 관계형 데이터베이스로 구현하려면 다음 원칙에 따라 테이블로 바꾼다.

EER 요소 관계형 테이블 변환
엔티티 타입 독자적인 테이블
1:1, 1:N 관계 타입 별도 테이블 없이 외래 키로 표현 (예: 주문 테이블에 고객번호 외래 키)
M:N 관계 타입 독자적인 테이블 (예: 주문상품 테이블 = 주문번호 + 상품번호 + 수량)
일반화(해체·전체) 상위 클래스 테이블 + 하위 클래스별 테이블(공통 주 키로 연결)

이 원칙을 책마루 전체 데이터에 적용하면 고객, 상품, 주문, 주문상품, 결제, 카드결제, 계좌이체 테이블 일곱 개로 정리된다. 여기에 3장에서 이미 다룬 "직원이 주문을 포장한다"는 관계까지 더하면, 책마루 온라인 주문 시스템의 데이터 구조 전체가 이 장에서 배운 개념만으로 표현 가능하다는 걸 확인할 수 있다.

책마루 온라인 주문 시스템 정보 모델 종합

지금까지 조각조각 다룬 엔티티와 관계를 하나로 모으면 책마루 온라인 주문 시스템의 정보 모델 전체 그림이 된다. 실제 ERD는 그림으로 그리지만, 여기서는 지금까지 정의한 요소를 모아 문장으로 정리해본다.

이 그림 하나로 "재고가 매장과 온라인에서 따로 논다"던 이 장 도입부의 문제도 짚어볼 수 있다. 재고수량은 상품 엔티티 타입의 속성으로 단 한 번만 정의되고, 매장 POS와 온라인 주문 시스템 양쪽 모두 이 하나의 상품 엔티티를 참조하도록 설계하면 된다. 데이터를 어디에 저장할지 정보 모델링 단계에서 명확히 정리해두면, 이후 설계·구현 단계에서 "재고를 어느 시스템 기준으로 맞출 것인가"를 놓고 다시 고민할 필요가 없어진다.

핵심 정리

다음 장(10장, 객체지향 분석 기법)에서는 이 정보 모델에 "동작"을 더해 데이터와 행위를 하나로 묶는 객체지향적 관점으로 넘어간다. 이 장에서 만든 책마루의 결제 일반화 구조가, 다음 장에서는 각 결제 수단이 스스로 수행할 수 있는 동작까지 포함한 클래스로 확장되는 과정을 보게 될 것이다.

← 이전동적 모델링 다음 →객체지향 분석 기법