본문 바로가기
인공지능(AI)

기획서의 텍스트를 백엔드 코드로 번역하는 첫 단추: 비즈니스 요구사항 기반의 ERD(데이터베이스) 설계 실무

by triz-hong 2026. 3. 22.

앞선 포스팅들을 통해 우리는 투자자의 마음을 사로잡는 PEST 분석과, 프로젝트의 방향성을 명확히 하는 완벽한 IT 서비스 제안서(RFP)를 작성하는 방법을 알아보았습니다.
비즈니스 기획이 "무엇을, 왜 만들어야 하는가"를 정의했다면, 이제 백엔드 개발자가 등판할 차례입니다. 화려한 기획서의 텍스트들을 차가운 서버의 로직으로 번역하는 첫 번째 관문, 바로 **데이터베이스 모델링(ERD 설계)**입니다.
초보 개발자들은 단순히 "회원 정보니까 User 테이블 하나 만들면 되겠지"라고 접근하지만, 훗날 트래픽이 몰리고 비즈니스가 확장될 때 서버가 주저앉는 원인은 십중팔구 이 '첫 단추'인 DB 설계에서 비롯됩니다. 오늘은 비즈니스 요구사항을 완벽하게 담아내는 실무적인 RDBMS 설계와 정규화 과정에 대해 해부해 보겠습니다.

 

1. ERD(Entity Relationship Diagram), 단순한 표가 아닌 비즈니스의 거울
데이터베이스 설계는 단순히 데이터를 저장할 엑셀 표를 만드는 작업이 아닙니다. 비즈니스의 **'정책(Policy)'**을 데이터 간의 **'관계(Relationship)'**로 정의하는 고도의 추상화 작업입니다.
예를 들어 볼까요? 제가 현재 기획 및 개발을 진행 중인 **'대학생 지식재산권(IP) 거래 플랫폼, Univ-Market'**을 떠올려 보겠습니다. 이 플랫폼의 핵심 비즈니스 요구사항은 다음과 같습니다.
 * "학생(User)은 여러 개의 지식재산권(IP_Item)을 등록해 판매할 수 있다."
 * "학생(User)은 다른 사람의 지식재산권(IP_Item)을 여러 개 구매할 수 있다."
이 간단한 두 줄의 기획 텍스트에서 우리는 1:N(일대다) 관계와 N:M(다대다) 관계를 뽑아내야 합니다. 한 명의 유저가 여러 상품을 등록할 수 있으니 User와 Item은 1:N 관계입니다. 하지만 구매 측면에서 보면, 여러 유저가 여러 상품을 구매할 수 있으므로 N:M 관계가 성립하죠.

 

2. N:M 관계의 함정과 매핑 테이블(Mapping Table)을 통한 해소
관계형 데이터베이스(RDBMS)에서는 N:M 관계를 물리적으로 직접 구현할 수 없습니다. 이를 억지로 구현하려다 보면 데이터 중복(Anomaly)이 발생하여 데이터 무결성이 산산조각 납니다.
따라서 실무에서는 두 테이블 사이에 **'매핑 테이블(교차 테이블)'**을 두어 두 개의 1:N 관계로 풀어냅니다. Univ-Market의 예시라면 Transaction(거래 내역)이라는 테이블이 그 역할을 하게 됩니다.
-- Univ-Market 비즈니스 로직을 반영한 DDL (MySQL 기준)

-- 1. 사용자 테이블 (판매자이자 구매자)
CREATE TABLE Users (
    user_id INT AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL,
    nickname VARCHAR(50) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 2. 지식재산권(IP) 상품 테이블
CREATE TABLE IP_Items (
    item_id INT AUTO_INCREMENT PRIMARY KEY,
    seller_id INT NOT NULL,  -- 1:N 관계 (누가 등록했는가)
    title VARCHAR(200) NOT NULL,
    price DECIMAL(10, 2) NOT NULL,
    FOREIGN KEY (seller_id) REFERENCES Users(user_id) ON DELETE CASCADE
);

-- 3. 거래 내역 테이블 (Users와 IP_Items의 N:M 관계를 해소하는 매핑 테이블)
CREATE TABLE Transactions (
    transaction_id INT AUTO_INCREMENT PRIMARY KEY,
    buyer_id INT NOT NULL,   -- 누가 샀는가?
    item_id INT NOT NULL,    -- 무엇을 샀는가?
    purchase_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (buyer_id) REFERENCES Users(user_id),
    FOREIGN KEY (item_id) REFERENCES IP_Items(item_id)
);

코드 인사이트:
위 DDL(데이터 정의어)을 보면 Transactions 테이블이 N:M 관계의 해결사 역할을 하고 있습니다. 비즈니스 부서에서 "A 학생이 지금까지 구매한 모든 IP 목록을 뽑아주세요!"라고 요청하면, 백엔드 개발자는 이 매핑 테이블을 거쳐 JOIN 쿼리 한 번으로 데이터를 우아하게 추출할 수 있게 됩니다.
3. 실무 아키텍처: 정규화(Normalization)와 역정규화(Denormalization)의 밀당
초기 스타트업이나 토이 프로젝트에서는 무조건적으로 데이터 중복을 없애는 '3차 정규화'까지 빡빡하게 진행하는 것이 정석입니다.
하지만 서비스가 성장하고 MAU(월간 활성 사용자)가 수십만 단위로 넘어가면 이야기가 달라집니다. 페이지 하나를 렌더링하기 위해 DB 테이블 5~6개를 매번 JOIN 해야 한다면, DB I/O 부하로 인해 응답 속도는 처참해집니다.
이때 훗날 CTO나 시니어 백엔드 엔지니어가 결단해야 하는 것이 바로 **'역정규화(Denormalization)'**입니다. 읽기(Read) 성능을 극대화하기 위해, 데이터 무결성을 조금 포기하더라도 의도적으로 데이터를 중복 저장하는 것이죠. "원칙적으로는 틀렸지만, 비즈니스 속도를 위해서는 맞는" 고도의 아키텍처적 타협입니다.

 

실무 대처법: 외래키(Foreign Key) 제약 조건 무결성 에러 해결법
ERD를 예쁘게 짜고 실제 DB에 마이그레이션을 하다 보면 가장 많이 마주치는 에러가 있습니다.
바로 Cannot add or update a child row: a foreign key constraint fails 에러입니다.
이는 자식 테이블(위 예시의 Transactions)에 데이터를 넣으려고 하는데, 부모 테이블(Users나 IP_Items)에 해당 ID가 존재하지 않을 때 DB가 멱살을 잡고 거부하는 현상입니다.
해결책:
 * 데이터 삽입 순서 확인: 반드시 부모 테이블(Users)에 데이터를 먼저 INSERT 한 뒤, 자식 테이블(Transactions)에 데이터를 넣어야 합니다. 테스트 코드를 짤 때 이 순서가 꼬이는 경우가 90%입니다.
 * 소프트 삭제(Soft Delete) 도입: 실무에서는 회원이 탈퇴했다고 해서 Users 테이블에서 레코드를 물리적으로 DELETE 해버리면 안 됩니다. 그러면 그 회원이 남긴 거래 내역, 게시글의 외래키가 전부 고아(Orphan)가 되어 시스템이 뻗습니다. 대신 is_deleted = TRUE 와 같은 상태 컬럼을 두어 논리적으로만 삭제 처리하는 것이 백엔드 실무의 기본 소양입니다.

 

4. 마무리 (기획자와 개발자가 같은 꿈을 꾸는 곳)
잘 짜인 ERD는 훌륭한 기획서 그 자체입니다. ERD만 봐도 "아, 이 서비스는 어떤 데이터를 중요하게 생각하고, 유저들이 어떤 상호작용을 하는구나"를 한눈에 파악할 수 있죠.
기획서의 추상적인 비즈니스 텍스트를 논리적인 데이터 구조로 완벽하게 번역해 냈을 때, 비로소 견고한 백엔드 API 개발을 시작할 수 있는 튼튼한 지반이 마련됩니다. 다음 포스팅에서는 이렇게 설계된 데이터베이스를 바탕으로, 프론트엔드와 안전하고 빠르게 데이터를 주고받는 'RESTful API 설계 원칙과 버저닝(Versioning) 전략'에 대해 알아보겠습니다.