도메인 개념과 데이터베이스 테이블이 일대일이 아닌 이유
모든 테이블을 도메인 객체와 리포지토리로 복제하지 않고 비즈니스 중요도, 위계와 행동을 기준으로 모델링하는 법을 설명합니다.
출처 및 AI 안내: 이 글은 제미니의 개발실무 유튜브를 기반으로 작성되었습니다.
gpt-5.6-sol모델을 사용해 생성·편집했습니다.
도메인 개념 수는 데이터베이스 테이블 수보다 적을 수 있다. 영속성 구조와 비즈니스 의미가 서로 다른 질문에 답하기 때문이다. 여러 테이블이 하나의 비즈니스 개념을 구성할 수 있고, 관계 테이블은 별도의 도메인 객체와 리포지토리가 아니라 구현 세부사항으로 남을 수 있다.
중요한 것은 클래스와 테이블의 수를 맞추는 일이 아니다. 제품에서 어떤 개념이 중요한지, 서로의 중요도와 위계는 어떠한지, 두 개념이 어떻게 관계 맺는지를 정하는 일이다.
하나의 개념을 여러 테이블에 저장할 수 있다
쿠폰이라는 개념이 쿠폰, 조건, 대상, 블랙리스트 테이블에 나뉘어 저장될 수 있다. 영속성 때문에 데이터를 분리했어도 애플리케이션의 행동은 이들을 하나의 쿠폰 개념으로 다룰 수 있다. 테이블 네 개가 같은 급의 도메인 객체 네 개를 강제하지 않는다.
반대 방향의 지름길도 위험하다. 엔티티와 CRUD 리포지토리를 자동 생성할 수 있다는 이유만으로 테이블이 도메인이 되지는 않는다. 조인 레코드, 이관 과정의 구조, 보조 클래스는 구현을 위해 필요해도 도메인 개념이 아닐 수 있다.
도메인 쪽 리포지토리는 상품 추가라는 작업 하나를 노출하고, 영속성 구현은 상품 행과 필요한 관계 행을 함께 기록할 수 있다. 바깥 인터페이스가 내부의 모든 저장소를 하나씩 복제할 필요는 없다.
상품과 브랜드의 위상부터 정한다
상품, 브랜드, 상품-브랜드 테이블이 있다고 하자. 객체를 만들기 전에 이 제품에서 브랜드가 무엇인지 물어야 한다.
브랜드가 상품 검색을 위한 태그 수준의 부가 정보라면 상품과 같은 위상의 개념이 아닐 수 있다. 브랜드가 이미 존재하고 그 아래 상품을 추가한다면 브랜드가 더 상위 개념일 수 있다. 브랜드와 상품을 따로 추가하고 둘의 매핑을 별도 기능으로 다룬다면 그 연결 레코드를 최상위 도메인 개념으로 올릴 필요는 없을 수 있다.
스키마만으로는 이 모델들 가운데 하나를 선택할 수 없다. 비즈니스 맥락이 빠져 있기 때문에 개념의 위계를 먼저 명확히 해야 한다. 상품과 브랜드 가운데 무엇이 더 중요한지, 브랜드가 상품보다 먼저 존재하는지, 브랜드 없는 상품이 가능한지, 둘을 연결하는 일이 별도 기능인지 생각해야 한다.
리포지토리는 의미 있는 작업을 중심으로 만든다
“엔티티마다 CRUD가 필요하다”는 접근은 행동보다 저장소에서 출발한다. 도메인 쪽 리포지토리는 모델에 필요한 작업을 중심으로 만드는 편이 낫다. 구현 안에서는 여러 영속성 리포지토리와 쿼리를 조율할 수 있다.
상품과 브랜드 사례에서는 그 상황에 맞는 질문을 해야 한다. 두 개념은 각각 얼마나 중요하며 위계상 무엇이 더 높은가? 상품을 추가할 때 브랜드가 이미 존재하는가? 브랜드 없는 상품도 가능한가? 상품과 브랜드를 연결하는 일은 별도 기능인가? 답에 따라 브랜드를 중요한 개념으로 볼지, 부가 정보로 볼지, 상품 생성과 분리해 관리할지가 달라진다.
억지로 다르게 만드는 것에도 가치는 없다. 한 테이블과 한 도메인 객체가 적절한 경우도 많다. 다만 일대일 대응 자체가 목표가 되어서는 안 된다. 유용한 모델은 비즈니스의 의미와 행동을 반영하고, 영속성은 그 뒤에서 필요한 만큼의 테이블을 사용한다.