모든 글

하나의 DB에서도 도메인별 데이터 접근 경계를 나누는 법

같은 DB 테이블을 쓰더라도 엔티티와 저장소 경계를 나눠 외부 연동 코드로부터 핵심 규칙을 보호하는 방법을 설명한다.

출처 및 AI 안내: 이 글은 제미니의 개발실무 유튜브를 기반으로 작성되었습니다. gpt-5.6-sol 모델을 사용해 생성·편집했습니다.

하나의 데이터베이스를 공유한다고 애플리케이션의 모든 영역이 같은 데이터 접근 모델을 써야 하는 것은 아니다. 핵심 도메인과 외부 연동 흐름은 같은 테이블에 데이터를 저장하면서도 서로 다른 권한과 이름, 의존성이 필요할 수 있다. 두 영역에 같은 엔티티와 저장소를 주면 이 차이가 사라진다.

외부 제공자로부터 채워지는 상품 테이블을 생각해 보자. 수집 영역은 제공자 API를 호출하고, 응답을 변환하고, 새 상품을 추가하며, 동기화 과정에서 기존 행을 갱신한다. 핵심 영역은 이 상품을 정돈된 비즈니스 개념으로 사용한다. 읽을 필요는 있지만 외부 제공자가 소유한 데이터를 함부로 다시 써서는 안 될 수 있다.

여러 제공자 API에서 상품을 모으면 응답 모양과 식별 방식이 서로 다를 수 있다. 수집·정제 영역은 그 차이를 감수하면서 같은 상품 테이블을 채우고 최신 값으로 맞춘다. 반면 핵심 API가 필요한 것은 어느 제공자에서 어떤 절차로 들어왔는지가 아니라 서비스 안에서 사용할 수 있는 Product의 안정된 모습이다. 같은 행을 한쪽은 동기화 대상으로, 다른 한쪽은 보호할 개념으로 본다.

물리적인 자원은 같지만 책임은 다르다.

의미를 지켜야 하는 코드를 분리하라

핵심 영역은 Product 엔티티와 저장소를 담은 스토리지 모듈을 소유할 수 있다. 이 표현은 핵심이 수행할 수 있는 작업만 노출한다. 핵심의 관점에서 상품을 변경할 수 없어야 한다면, 저장소에서도 동기화 갱신 기능을 사용할 수 없게 해야 한다.

연동 영역은 같은 테이블에 매핑되더라도 자기 모듈 안에 별도의 엔티티와 데이터 접근 코드를 정의할 수 있다. 이 표현에는 외부 데이터 수집과 정규화에 필요한 제공자별 필드와 갱신 작업이 들어갈 수 있다. 핵심 스토리지 모듈은 이 연동 엔티티의 존재를 알 필요가 없다.

반대 방향의 의존성도 없어야 한다. 수집 모듈이 같은 테이블을 사용한다는 이유로 핵심 저장소를 가져다 쓰지 않는다. 자기 역할에 필요한 접근을 직접 소유한다. 두 영역은 제한 없는 하나의 저장소를 재사용하는 대신 저장된 데이터에서 만난다.

코드 구조에서는 핵심의 DB 모듈에 핵심용 Product와 읽기 중심 저장소를 두고, 연동 모듈에는 수집용 엔티티와 쓰기 기능을 둘 수 있다. 두 엔티티가 같은 테이블에 매핑되더라도 서로를 참조하지 않는다. 연동 코드가 외부 응답을 자기 엔티티로 삽입하거나 갱신해도, 핵심 코드는 그 생성 과정을 모른 채 정돈된 상품만 조회한다.

그러면 하나의 데이터베이스 안에 논리적 경계가 생긴다.

  • 연동 코드는 외부 제공자 데이터를 추가하고 동기화할 수 있다.
  • 핵심 코드는 수집 세부사항에 의존하지 않고 완성된 상품을 사용한다.
  • 제공자별로 지저분한 내용은 보호해야 할 모델 밖에 남는다.
  • 한쪽 엔티티의 변경이 다른 쪽에 그 작업을 자동으로 노출하지 않는다.

테이블 이름이 아키텍처 경계를 정하는 것이 아니다. 허용된 행위가 경계를 정한다.

하나의 공용 저장소가 지나치게 열릴 때

어디서나 핵심 저장소를 사용하는 방법도 가능하다. 작고 단순한 흐름이라면 충분할 수도 있다. 문제는 연동 책임이 커질 때 나타난다.

외부 데이터는 다루기 까다로운 경우가 많다. 수집 코드에는 정규화, 반복 갱신, 제공자 식별자, 핵심 용어에는 필요 없는 중간 값이 들어갈 수 있다. 공용 엔티티가 이 요구를 모두 흡수하면 핵심 모델이 시스템에서 가장 불안정한 경계의 모양을 따라가게 된다.

저장소의 권한도 문제가 된다. 갱신 메서드가 모든 모듈에 열려 있으면 원래 동기화 과정만 바꾸기로 한 데이터를 핵심 유스케이스도 수정할 수 있다. 리뷰에서 오용을 잡을 수는 있지만 코드는 여전히 그것을 허용한다. 접근 모델을 분리하면 기억했다가 리뷰에서 지켜야 할 관례가 아니라, 구현하는 순간 개발자가 마주치는 제약으로 의도를 바꿀 수 있다.

“핵심에서는 업데이트하지 않는다”는 주석이나 팀의 기억은 새 코드와 새 구성원이 들어올 때마다 다시 확인해야 한다. 핵심 저장소의 인터페이스에 수정 작업 자체가 없다면 잘못된 변경을 작성하는 단계부터 막을 수 있다. 수집 영역에는 동기화 책임에 필요한 쓰기를 열고 핵심 영역에는 그 권한을 닫는 차이가 코드에 남는다.

이것이 완전한 물리적 격리는 아니다. 두 모델은 여전히 같은 공유 자원에 닿으므로 스키마 변경이나 잘못된 쓰기가 양쪽에 영향을 줄 수 있다. 별도 저장소를 둘 만큼 필요가 확인되지 않은 시스템에서 코드 수준의 소유권 경계를 제공하는 방법이다.

따라서 이 구조로 DB 장애나 스키마 충돌까지 격리했다고 말할 수는 없다. 한쪽에서 테이블을 잘못 갱신하면 다른 쪽도 영향을 받는다. 얻는 것은 배포 단위나 물리 저장소의 분리가 아니라, 어떤 코드가 어떤 의도로 데이터를 다룰 수 있는지에 대한 논리적 통제다. 현재 문제에 그 정도의 보호가 맞는지 확인해야 한다.

소유권을 표현하는 가장 작은 경계부터 시작하라

처음부터 모든 외부 연동마다 별도 스토리지 모듈을 만들 수도 있다. 타당한 선택일 수 있지만 가치가 분명해지기 전에 구조가 늘어난다. 더 작은 출발점은 외부 엔티티와 저장소를 연동 모듈 안에 두고 경계 밖의 참조를 막는 것이다.

필요하면 나중에 이 영역을 강화할 수 있다. 규모가 커지거나, 독립 배포되거나, 자체적으로 보호할 규칙이 생기면 내부 모듈을 더 명확하게 나눌 수 있다. 초기의 중요한 결정은 핵심 영역을 관계없는 책임까지 모두 가져다 쓰는 기본 스토리지 라이브러리로 만들지 않는 것이다.

데이터 접근을 나누기 전에 다음을 물어보자.

  • 누가 이 데이터를 생성하고 갱신할 수 있는가?
  • 어느 영역에는 안정적인 읽기 관점만 필요한가?
  • 외부 제공자 전용 필드가 핵심 모델로 새어 들어오는가?
  • 공용 저장소가 한쪽에서 호출하면 안 되는 작업까지 노출하는가?
  • 지금 새 DB나 서비스를 만드는 것보다 엔티티 매핑을 하나 더 두는 편이 단순한가?

두 영역의 생명주기와 권한이 실제로 같다면 모델 분리는 불필요한 중복일 수 있다. 같은 행을 쓰지만 소유권이 다르다면 그 중복은 유용한 정보를 담는다. 시스템의 두 부분이 서로 다른 이유로 같은 데이터를 바라보고 있다는 정보다.

데이터베이스 경계는 테이블 하나에 코드베이스 전체의 엔티티도 하나여야 한다는 규칙이 아니라, 제품이 필요로 하는 보호 수준을 따라야 한다. 핵심의 관점을 좁게 유지하고, 연동 코드에는 역할에 필요한 쓰기 권한을 주며, 운영이 정당화할 때만 경계를 더 정교하게 만들자.