그림에서 코드로 가는 다리
12장에서 좋은 설계의 기준 — 응집도와 결합도 — 을 세웠다면, 이번 장은 그 기준을 실제로 어떻게 적용해 설계 산출물을 만드는가를 다룬다. 6~9장에서 책마루의 요구사항을 자료흐름도(DFD)로 분석했다는 걸 떠올려보자. DFD는 "무엇이 어디서 어디로 흐르는가"를 논리적으로 보여줄 뿐, 실제 프로그램이 어떤 모듈들로 구성될지는 말해주지 않는다. 그 사이를 잇는 것이 이 장에서 다루는 구조적 설계(structured design) 기법이다.
구조적 설계는 DFD를 **구조도표(structure chart)**라는, 모듈 간 호출 관계를 트리 형태로 표현한 그림으로 옮긴다. DFD의 각 처리 버블(bubble)이 구조도표의 모듈이 되고, DFD의 화살표(자료흐름)가 모듈 사이를 오가는 매개변수가 된다. 이 장의 앞부분에서는 이 전환 과정을, 뒷부분에서는 정보 모델링(9장)의 결과인 ER 다이어그램을 실제 데이터베이스 테이블로 옮기는 과정을 다룬다.
구조도표의 표기법
구조도표는 사각형(모듈)과 화살표(호출 및 데이터 흐름)로 구성된다. 화살표에 속이 빈 작은 원이 달려 있으면 데이터 흐름(data flow)을, 속이 채워진 원이 달려 있으면 제어 흐름(control flow, 즉 플래그)을 뜻한다. 위쪽 모듈이 아래쪽 모듈을 호출하는 계층 구조로 그려지며, 이 계층은 12장에서 다룬 응집도·결합도 원칙에 따라 다듬어져야 한다.
두 가지 정보 흐름 유형
DFD를 구조도표로 바꾸기 전에, 먼저 그 DFD가 어떤 성격의 자료 흐름을 가지고 있는지 파악해야 한다. 자료 흐름은 크게 두 가지로 나뉜다.
- 변환흐름(transform flow): 입력을 받아 가공한 뒤 새로운 출력을 만들어내는 흐름. 입력은 정제되고, 그 정제된 결과가 변환되어, 출력으로 나간다. 책마루의 "주문 총액을 계산한다" 기능이 여기 해당한다 — 장바구니 항목(입력)을 받아 할인·배송비를 계산(변환)하고 최종 결제 금액(출력)을 낸다.
- 트랜잭션흐름(transaction flow): 입력값에 따라 서로 다른 경로 중 하나를 선택해 처리하는 흐름. 책마루의 "배송 방법을 결정한다" 기능이 여기 해당한다 — 배달 주소(입력)를 받아 "도보 30분 이내인가"를 판단(트랜잭션 중심)한 뒤, 동네 배달 처리와 택배사 연동 처리 중 하나의 경로로 갈라진다(서로 다른 출력).
대부분의 실제 시스템은 전체적으로는 변환흐름이 지배적이면서 일부 지점에서 트랜잭션흐름이 섞여 나타난다. 책마루 시스템 전체를 보면 "주문 접수 → 처리 → 배송"이라는 큰 흐름은 변환흐름이지만, 그 안의 "배송 방법 결정" 지점은 트랜잭션흐름이다.
변환흐름 중심 설계
변환흐름으로 판단된 DFD는 세 부분으로 나눠 구조도표로 옮긴다.
- 입력흐름(incoming flow): 외부에서 들어온 정제되지 않은 원시 데이터를, 시스템이 사용할 수 있는 정제된 형태로 바꾸는 부분
- 변환중심(transform center): 정제된 입력을 실제로 가공해 결과를 만들어내는 핵심 처리 부분
- 출력흐름(outgoing flow): 가공된 결과를 외부에 보여줄 형태로 바꾸는 부분
책마루의 "주문 총액을 계산한다" DFD를 예로 들어보자. 손님이 담은 장바구니 원본 데이터(상품 코드와 수량 목록)가 입력흐름을 통해 "가격이 채워진 항목 목록"으로 정제되고, 변환중심에서 할인율 적용과 배송비 계산이 이루어지며, 출력흐름에서 그 결과가 "최종 결제 금액 표시" 형태로 정리된다.
이 세 구간의 경계를 정한 뒤, 구조도표의 최상위에는 전체를 조율하는 모듈 하나를 두고 그 아래에 입력 담당·변환 담당·출력 담당 모듈 셋을 배치한다. 이 작업을 **최상위 수준의 세분화(first level factoring)**라 부른다.
계산 총괄 (전체 모듈)
├── 장바구니 정제 (입력 제어 모듈)
├── 금액 계산 (변환 제어 모듈)
└── 결제 화면 구성 (출력 제어 모듈)
이후 각 하위 모듈을 다시 한 단계씩 세분화해 나간다. 입력 제어 모듈 아래에는 "상품 코드 검증", "재고 가격 조회" 같은 더 구체적인 모듈이 배치되고, 변환 제어 모듈 아래에는 "할인율 계산", "배송비 계산" 모듈이, 출력 제어 모듈 아래에는 "금액 문자열 포맷팅" 모듈이 배치되는 식이다. 이 세분화는 상위 계층부터 하위 계층으로 내려가는 하향식(top-down) 접근을 따르며, 각 단계마다 응집도·결합도 기준으로 결과를 다듬는 정제 과정을 거친다.
트랜잭션흐름 중심 설계
트랜잭션흐름으로 판단된 DFD는 다른 방식으로 구조도표를 만든다. 핵심은 트랜잭션 중심(transaction center) — 즉 "어떤 경로로 갈지 결정하는 지점" — 을 찾아내는 것이다.
책마루의 "배송 방법을 결정한다" DFD에서 트랜잭션 중심은 "배달 가능 여부 판정" 버블이다. 이 판정 결과에 따라 처리는 두 갈래(동네 배달 처리 경로, 택배 연동 처리 경로) 중 하나로 나뉜다. 구조도표는 이 구조를 그대로 반영한다.
배송 처리 총괄 (전체 제어)
├── 배달 가능 여부 판정 (입력 제어)
└── 배송 경로 분기 (트랜잭션 제어)
├── 동네 배달 등록 (동작 경로 1)
└── 택배사 연동 등록 (동작 경로 2)
트랜잭션 제어 모듈 아래에 각 동작 경로가 하나씩 배치되고, 각 경로는 다시 변환흐름 특성을 가질 수도, 트랜잭션흐름 특성을 가질 수도 있다. 예를 들어 "택배사 연동 등록" 경로 안에서 다시 "어느 택배사를 쓸지" 결정하는 트랜잭션흐름이 나타날 수 있다 — 이 경우 그 경로 안에서 다시 트랜잭션흐름 설계를 적용한다. 이렇게 변환흐름과 트랜잭션흐름 설계는 하나의 시스템 안에서 필요에 따라 섞여 사용된다.
두 기법 모두 마지막 단계는 정제다. 기계적으로 만들어진 초기 구조도표에는 12장에서 다룬 결합도·응집도 원칙이 자동으로 반영되지 않는다. 예를 들어 세분화 과정에서 우연히 절차적 응집도를 가진 모듈이 생겼다면, 이를 함수적 응집도를 갖도록 다시 쪼개는 수작업 정제가 필요하다. 즉 자료 흐름 중심 설계는 설계자에게 출발점이 되는 뼈대를 제공할 뿐, 최종 품질은 설계자가 직접 다듬어야 완성된다.
데이터베이스 설계로 넘어가기
지금까지는 시스템의 동적인 처리 흐름을 구조로 옮기는 이야기였다. 이제부터는 시스템의 정적인 데이터를 실제로 어떻게 저장할지를 다룬다. 9장에서 정보 모델링으로 만든 책마루의 개체(엔티티)와 관계 — 상품, 재고, 주문, 고객 — 를 관계형 데이터베이스의 테이블로 옮기는 과정이다.
정보 모델은 데이터를 개념적(conceptual) 차원에서만 다룬다. "주문은 여러 상품을 포함한다"는 관계는 표현하지만, 그 관계가 실제로 어떤 테이블에 어떤 컬럼으로 저장될지는 아직 정하지 않은 상태다. ER 모델을 관계형 모델로 매핑하는 작업이 바로 이 개념적 설계를 물리적으로 구현 가능한 형태로 바꾸는 일이다.
매핑의 기본 원리는 단순하다 — 개체는 대체로 테이블이 되고, 개체의 속성은 컬럼이 되며, 개체 사이의 관계는 외래 키(foreign key)로 표현된다. 책마루 예로 보면 이렇다.
| ER 모델 요소 | 관계형 테이블 요소 |
|---|---|
| 상품 개체 | 상품 테이블 (상품코드, 상품명, 가격) |
| 주문 개체 | 주문 테이블 (주문번호, 주문일시, 고객번호) |
| 주문-상품 관계(다대다) | 주문상세 테이블 (주문번호, 상품코드, 수량) — 관계 자체가 테이블이 됨 |
| 고객 개체 | 고객 테이블 (고객번호, 이름, 배달주소) |
일대다 관계는 "다(多)" 쪽 테이블에 "일(一)" 쪽의 기본 키를 외래 키로 추가하는 것으로 표현된다(예: 주문 테이블의 고객번호). 다대다 관계 — 하나의 주문에 여러 상품이 담기고, 하나의 상품이 여러 주문에 등장하는 관계 — 는 두 개체의 기본 키를 함께 담는 별도의 연결 테이블(주문상세)을 만들어 표현한다. 이렇게 매핑된 테이블들이 만족해야 할 제약 조건도 함께 정의된다 — 각 컬럼이 가질 수 있는 값의 범위를 정하는 도메인 제약, 테이블 안에서 각 행을 유일하게 식별하는 키 제약, 외래 키가 참조하는 값이 실제로 존재해야 한다는 참조 무결성 제약 등이다.
정규화 — 중복을 줄이는 원칙
테이블을 매핑한 뒤에는 그 구조가 데이터 중복과 이상 현상(anomaly)을 일으키지 않는지 점검해야 한다. 이 점검 기준이 **정규화(normalization)**다. 정규화되지 않은 테이블은 같은 정보가 여러 행에 중복 저장되어, 한 곳을 수정하면 나머지도 일일이 따라 고쳐야 하는 문제를 낳는다.
책마루의 주문상세 테이블을 예로 정규화 단계를 살펴보자. 만약 이 테이블에 주문번호, 상품코드, 수량뿐 아니라 상품명과 상품가격까지 함께 저장했다고 하자.
| 주문번호 | 상품코드 | 상품명 | 상품가격 | 수량 |
|---|---|---|---|---|
| 1001 | B001 | 소설 문고본 | 12000 | 2 |
| 1001 | S002 | 스프링노트 | 3000 | 1 |
| 1002 | B001 | 소설 문고본 | 12000 | 1 |
이 테이블에는 두 가지 문제가 있다. 상품코드 B001의 상품명이 여러 행에 중복 저장되어 있고(중복 이상), 나중에 "소설 문고본"의 가격이 바뀌면 이 테이블 전체를 뒤져 관련된 모든 행을 수정해야 한다(갱신 이상). 정규화는 이런 이상 현상을 컬럼 사이의 함수적 종속 관계를 분석해 단계적으로 제거한다.
- 1정규형(1NF): 모든 컬럼이 더 이상 쪼갤 수 없는 원자값만 가져야 한다. 한 컬럼에 여러 값을 쉼표로 나열하거나 배열로 넣는 것은 1NF 위반이다.
- 2정규형(2NF): 1NF를 만족하면서, 기본 키가 아닌 모든 컬럼이 기본 키 전체에 완전히 종속되어야 한다. 위 테이블에서 상품명·상품가격은 기본 키(
주문번호+상품코드) 전체가 아니라상품코드에만 종속되므로 2NF를 위반한다 — 이를 해결하려면 상품명·상품가격을 별도의상품테이블로 분리해야 한다. - 3정규형(3NF): 2NF를 만족하면서, 기본 키가 아닌 컬럼끼리 서로 종속되는 이행적 종속(transitive dependency)이 없어야 한다.
정규화를 거친 뒤 책마루의 주문 데이터는 주문상세(주문번호, 상품코드, 수량)와 상품(상품코드, 상품명, 상품가격) 두 테이블로 분리된다. 상품 가격이 바뀌면 상품 테이블 한 행만 고치면 되고, 모든 주문 내역은 자동으로 최신 가격 참조 구조를 유지한다(단, 이미 확정된 과거 주문의 결제 금액은 별도로 스냅샷을 남겨두는 것이 실무에서는 일반적이다 — 이는 이 장의 범위를 넘는 설계 판단이다).
정규화는 만능이 아니다: 정규화를 지나치게 밀어붙이면 조회할 때마다 여러 테이블을 조인(join)해야 해서 성능이 떨어질 수 있다. 책마루처럼 조회가 빈번하고 데이터 양이 크지 않은 시스템에서는 3NF 정도에서 멈추고, 통계용 조회 화면처럼 특수한 경우에만 의도적으로 비정규화를 적용하는 것이 실용적이다.
뷰 — 필요한 것만 보여주는 창
정규화된 실제 테이블(physical table) 외에, 데이터베이스는 **뷰(view)**라는 가상 테이블을 정의할 수 있다. 뷰는 실제 테이블에 연산을 가해 만든 결과를 마치 테이블처럼 보여주는 것이다. 책마루라면 매장 직원용으로 "오늘 처리해야 할 미배송 주문 목록"이라는 뷰를, 사장님용으로 "이번 달 상품별 매출 합계"라는 뷰를 정의할 수 있다. 직원은 재고·결제 관련 원본 테이블을 직접 볼 필요 없이 이 뷰만 조회하면 되고, 뷰를 통해 접근 권한을 제한하는 효과도 얻을 수 있다.
핵심 정리
- 구조적 설계는 DFD의 자료 흐름 유형(변환흐름 또는 트랜잭션흐름)을 판단해 구조도표로 옮기며, 이후 응집도·결합도 기준으로 수작업 정제를 거쳐야 완성된다.
- 변환흐름 설계는 입력흐름·변환중심·출력흐름 세 구간을 나누고, 트랜잭션흐름 설계는 트랜잭션 중심에서 갈라지는 동작 경로별로 모듈을 배치한다.
- ER 모델의 개체는 테이블로, 관계는 외래 키(또는 다대다의 경우 별도 연결 테이블)로 매핑된다.
- 정규화는 함수적 종속 관계를 분석해 데이터 중복과 갱신 이상을 제거하는 과정이며, 1NF·2NF·3NF로 단계적으로 진행된다.
다음 장(14장)에서는 지금까지 만든 모듈 구조를 실제로 구현할 때 반복적으로 마주치는 설계 문제들 — 결제 수단을 유연하게 확장하거나, 서로 다른 택배사 API를 하나의 인터페이스로 통합하는 것 같은 — 에 이미 검증된 해법을 제공하는 디자인 패턴을 다룬다.