🟣 코드부터 짜면 왜 무너지나
처음 객체지향 프로그램을 짤 때는 요구사항을 듣자마자 클래스를 만들었다. "주문 기능을 만들어라"는 말에 곧바로 OrderManager를 열고 메서드를 채웠다. 작은 과제에서는 그럭저럭 돌아갔지만, 조금만 커지면 클래스 하나가 온갖 일을 떠안았고 이름과 실제 하는 일이 어긋나기 시작했다. 무엇이 잘못됐는지 짚기 어려웠다.
원인은 두 가지 서로 다른 활동을 한 단계로 뭉갠 데 있었다. 무엇을 만들 것인가를 이해하는 일과 그것을 어떻게 지을 것인가를 정하는 일은 성격이 다르다. 앞을 분석, 뒤를 설계라고 부른다. Larman은 이 둘을 "옳은 것을 만드는 일"과 "그것을 옳게 만드는 일"로 구분한다. 코드부터 짜면 이 두 판단이 머릿속에서 동시에 섞여 둘 다 흐려진다.
객체지향 개발은 대체로 세 단계를 밟는다. 먼저 문제 영역에서 중요한 개념들을 찾아내고(분석), 그 개념들을 바탕으로 책임을 가진 소프트웨어 객체를 정의하고(설계), 마지막으로 그것을 특정 언어의 코드로 옮긴다(구현). 이 장은 첫 단계, 곧 문제를 먼저 그리는 도구인 도메인 모델을 다룬다.
🟣 도메인 모델은 소프트웨어가 아니다
도메인 모델은 문제 영역에 등장하는 개념들과 그 사이의 관계를 그린 그림이다. 매장 판매 시스템이라면 판매, 상품, 결제, 계산대 같은 것들이 개념이다. 여기서 가장 자주 하는 오해가, 이 그림을 곧바로 만들 클래스 설계도로 여기는 것이다. 도메인 모델은 소프트웨어 클래스가 아니다. 메서드도, 데이터베이스 테이블도 아니다. 현실 세계에 이미 존재하는 개념을 개발자와 이해관계자가 같은 언어로 부르기 위한 사전에 가깝다.
그래서 도메인 모델의 클래스에는 연산(메서드)을 붙이지 않는다. 개념이 무엇을 아는지(속성)와 개념들이 어떻게 엮이는지(연관)만 그린다. "판매는 여러 개의 판매 항목으로 이뤄진다", "각 판매 항목은 하나의 상품을 가리킨다" 같은 문장이 그대로 그림이 된다.
숫자 1과 *는 다중도다. 한 판매가 여러 판매 항목을 포함하고, 여러 판매 항목이 각각 하나의 상품을 가리킨다는 뜻이다. 이 그림에는 아직 "총액을 계산한다" 같은 동작이 하나도 없다. 무엇이 있고 어떻게 엮이는지만 있다. 동작을 누구에게 맡길지는 다음 장의 설계에서 정한다.
🟣 개념은 어디서 찾나
도메인 모델을 그리려면 개념을 찾아야 한다. 실무에서 쓰는 방법은 소박하다. 요구사항 문장에서 명사와 명사구를 뽑아내는 것이다. "고객이 상품을 장바구니에 담고 결제한다"에서 고객, 상품, 장바구니, 결제가 후보로 올라온다. 완벽한 방법은 아니다. 같은 개념이 다른 이름으로 불리기도 하고, 어떤 명사는 개념이 아니라 속성일 뿐이다. 그래도 빈 화면 앞에서 시작점을 잡아 주는 값이 있다.
🚀 속성인가 개념인가
명사를 뽑고 나면 한 가지를 판단해야 한다. 이것을 독립된 개념으로 세울지, 다른 개념의 속성으로 둘지다. Larman의 기준은 단순하다. 현실에서 그것이 숫자나 문자 같은 단순 값이 아니라면 개념일 가능성이 높다. 예를 들어 "매장"은 이름과 주소를 가지므로 개념이고, "주소"는 그 자체로 여러 정보를 담으니 역시 개념으로 승격할 수 있다. 반면 "가격"은 단순한 금액 값이라 상품의 속성으로 두는 편이 자연스럽다.
이 판단이 중요한 이유는, 잘못 두면 나중 설계에서 그대로 비용이 되기 때문이다. 속성이어야 할 것을 개념으로 부풀리면 쓸데없는 클래스가 늘고, 개념이어야 할 것을 속성에 욱여넣으면 정보가 한 클래스에 뭉친다.
🟣 표현적 차이를 줄인다
도메인 모델을 왜 굳이 그리느냐는 질문에 가장 설득력 있던 답이 표현적 차이(representational gap)였다. 현실의 개념과 코드의 객체가 서로 닮을수록, 요구사항이 바뀌었을 때 코드의 어디를 고쳐야 하는지가 곧바로 보인다. 도메인 모델에 판매와 판매 항목이 따로 있고 설계의 소프트웨어 객체도 Sale과 SalesLineItem으로 나뉘어 있다면, "항목별 할인" 요구가 들어왔을 때 손댈 자리가 자명하다.
반대로 처음부터 OrderManager 하나에 모든 걸 몰아넣으면, 현실에는 있는 "판매 항목"이라는 개념이 코드 어디에도 없다. 요구가 바뀔 때마다 그 거대한 클래스 안을 헤매게 된다. 내가 처음에 겪은 붕괴가 정확히 이것이었다. 도메인 모델은 그 차이를 미리 좁혀 두는 장치였다.
물론 도메인 모델의 개념이 언제나 소프트웨어 클래스로 일대일 옮겨지는 것은 아니다. 어떤 개념은 설계 단계에서 합쳐지거나 사라지고, 도메인에는 없던 순수 소프트웨어 객체(예를 들어 화면 제어자나 저장소)가 새로 생긴다. 도메인 모델은 정답 설계도가 아니라 설계의 출발점이자 공용 어휘일 뿐이라는 점을 놓치면, 이 그림을 데이터베이스 스키마처럼 떠받들게 된다.
🟣 정리
- 분석은 무엇을 만들지 이해하는 일이고 설계는 그것을 어떻게 지을지 정하는 일이다. 코드부터 짜면 이 둘이 섞여 판단이 흐려진다.
- 도메인 모델은 문제 영역의 개념과 관계를 그린 그림이지 소프트웨어 클래스가 아니다. 속성과 연관만 담고 메서드는 넣지 않는다.
- 개념은 요구사항의 명사에서 찾되, 단순 값이면 속성으로 두고 여러 정보를 담으면 독립 개념으로 세운다.
- 도메인 모델의 목적은 표현적 차이를 줄이는 것이다. 현실 개념과 코드 객체가 닮을수록 변경 지점이 명확해진다.
- 도메인 모델은 정답 설계도가 아니라 공용 어휘이자 설계의 출발점이다. 스키마처럼 떠받들지 않는다.
면접에서 "설계를 어떻게 시작하느냐"는 질문에, 나는 요구사항에서 개념부터 뽑아 관계를 그린 뒤 소프트웨어 객체로 옮긴다고 답한다. 이 과정을 실제로 밟아 본 근거가 도메인 모델이다. 그런데 개념까지는 그렸는데, 정작 "총액 계산" 같은 동작을 어느 객체에 맡길지는 아직 비어 있다. 그 빈자리를 채우는 원칙이 다음 장의 책임 주도 설계와 GRASP다.