Files
project-auth-server/docs/standards/design/dto-domain-entity-separation.md
T

5.6 KiB

DTO / Domain / Entity separation 기준

목적

DTO, Domain, Entity는 이름만 다른 비슷한 객체가 아니라 서로 다른 경계와 책임을 가진 모델이다.
형태가 비슷하더라도 역할이 다르면 분리한다.

공식 의미

  • DTO는 프로세스/경계 사이에서 데이터를 운반하는 객체다.
  • Entity는 persistence provider가 관리하는 영속 모델이다.
  • Spring request binding은 강력하지만, 외부 입력을 객체 그래프에 직접 바인딩하는 것은 보안상 주의가 필요하다.
  • 따라서 web 입력 모델, 비즈니스 모델, persistence 모델을 하나로 섞지 않는 것이 안전하다.

기본 규칙

1. DTO는 경계 데이터 모델이다

DTO는 다음 경계에서만 사용한다.

  • HTTP request / response
  • external API request / response
  • message payload
  • batch/file I/O payload

DTO의 책임:

  • 데이터 운반
  • 직렬화/역직렬화 경계 표현
  • validation annotation 수용 가능
  • API 계약 표현

DTO의 책임이 아닌 것:

  • 비즈니스 규칙
  • 도메인 불변식 유지
  • 영속성 상태 관리
  • repository/service 호출

2. Domain은 비즈니스 의미 모델이다

Domain은 다음을 표현한다.

  • entity
  • value object
  • domain service
  • domain policy
  • 불변식
  • 상태 전이 의미

Domain의 책임:

  • 비즈니스 의미
  • 유효한 상태 보장
  • 행위와 규칙 표현

Domain의 책임이 아닌 것:

  • JSON 구조
  • HTTP 요청/응답 형식
  • JPA annotation/persistence lifecycle
  • 외부 API payload 구조

3. Entity는 persistence 모델이다

Entity는 DB와 persistence provider가 다루는 모델이다.

Entity의 책임:

  • 테이블/컬럼/관계 매핑
  • persistence lifecycle 대응
  • 저장 가능한 상태 표현

Entity의 책임이 아닌 것:

  • API 응답 계약
  • 비즈니스 정책 결정
  • 외부 연동 payload 표현

4. 하나의 타입으로 세 역할을 겸하지 않는다

다음은 기본 금지한다.

  • request DTO를 그대로 domain으로 사용
  • JPA entity를 그대로 response DTO로 반환
  • domain object에 JPA/JSON/validation annotation을 한꺼번에 섞기

형태가 비슷해도 책임이 다르면 타입을 분리한다.

5. 경계마다 변환을 명시한다

기본 흐름:

  • request DTO -> command / domain input
  • entity -> domain
  • domain -> response DTO
  • external payload -> internal model

변환은 mapper/assembler/factory 같은 명시적 경계에서 수행한다.

6. DTO에는 validation을 둘 수 있지만, 최종 의미 검증은 domain이 맡는다

DTO에 둘 수 있는 것:

  • @NotBlank
  • @Size
  • 형식 검증
  • 웹 입력 범위 검증

하지만 다음은 domain 책임이다.

  • 불변식
  • 상태 전이 타당성
  • 정책 기반 허용 여부
  • aggregate 일관성

즉 DTO validation이 domain validation을 대체하지 않는다.

7. Entity의 nullable/관계/지연로딩 특성을 domain에 그대로 전파하지 않는다

DB 제약 때문에 entity가 nullable/관계 중심일 수는 있다.
하지만 domain은 비즈니스 invariant 기준으로 더 엄격할 수 있다.

기본:

  • entity -> domain 변환 시 검증/정규화
  • domain은 persistence 편의보다 비즈니스 의미 우선

8. Response DTO는 domain을 그대로 노출하지 않는다

response는 외부 계약이다.
기본적으로 domain object를 그대로 JSON으로 노출하지 않는다.

이유:

  • 내부 구조 변경이 외부 계약에 새어 나간다
  • 민감정보/불필요 필드 노출 위험
  • 직렬화 shape가 domain 설계를 오염시킨다

9. Request DTO를 entity에 직접 바인딩하지 않는다

Spring DataBinder/WebDataBinder는 강력하지만 보안상 주의가 필요하다.
따라서 외부 입력을 entity나 깊은 도메인 객체에 직접 바인딩하지 않는다.

기본:

  • request DTO에만 바인딩
  • 이후 명시적 변환을 거쳐 command/domain으로 이동

10. Entity를 domain과 1:1로 맞추려 하지 않는다

entity와 domain은 비슷할 수 있지만 항상 같아야 할 필요는 없다.

예:

  • entity는 FK/nullable/지연 로딩 중심
  • domain은 value object/invariant/행위 중심

“필드가 같아 보이니 하나로 합친다”를 금지한다.

11. DTO는 record/단순 데이터 구조를 우선 검토

DTO는 경계 데이터 운반이 목적이므로, 불변/단순 구조를 우선 검토한다. 단, framework binding/serialization 요구사항이 있으면 그 제약을 따른다.

12. Domain은 DTO naming을 따라가지 않는다

도메인 타입 이름은 API 필드명/JSON 필드명보다 비즈니스 의미를 우선한다.

금지 예:

  • UserResponseName
  • ProviderRequestCode

도메인은 business language를 사용한다.

13. Entity는 persistence 편의 메서드를 가질 수 있지만 도메인 규칙 중심 타입이 되지 않게 한다

entity에 persistence 편의 메서드가 있을 수는 있다.
하지만 핵심 비즈니스 규칙을 entity/JPA lifecycle에 과도하게 묶지 않는다.

14. Mapping 비용보다 경계 명확성이 더 중요하다

DTO/Domain/Entity 분리는 변환 코드가 늘 수 있다.
그러나 그 비용보다:

  • 경계 명확성
  • 보안성
  • 변경 범위 제한
  • 직렬화/영속성 오염 방지 의 이익이 더 크다.

프로젝트 기준 요약

  • DTO = 경계 데이터
  • Domain = 비즈니스 의미
  • Entity = persistence 모델
  • 하나의 타입으로 세 역할 겸용 금지
  • request는 DTO에만 바인딩
  • DTO validation과 domain invariant를 분리
  • entity nullable/관계를 domain에 그대로 전파하지 않음
  • response DTO로 외부 계약을 명시