Files
project-auth-server/docs/standards/design/mapper-separation.md
T

5.3 KiB

mapper separation 기준

목적

매퍼는 한 모델의 데이터를 다른 모델로 번역하는 역할만 맡는다.
비즈니스 규칙, 영속성 접근, 외부 호출, 부수효과를 매퍼에 섞지 않는다.

공식 의미

  • DTO는 프로세스/경계 사이에서 데이터를 옮기는 객체다.
  • DTO와 domain object 사이를 옮기는 assembler/mapper를 둘 수 있다.
  • Spring DataBinder는 입력 바인딩이 강력하지만, 바인딩 대상 설계를 신중히 해야 하며 보안상 주의가 필요하다.
  • MapStruct 같은 매퍼 도구는 type-safe bean mapping 코드를 생성한다.
  • 기존 target 객체 갱신은 @MappingTarget 같은 명시적 update mapping으로 표현할 수 있다.

기본 규칙

1. 매퍼는 “번역”만 한다

매퍼의 책임은 아래 중 하나다.

  • request DTO -> command
  • domain -> response DTO
  • persistence entity -> domain
  • domain -> persistence entity
  • external DTO -> internal model
  • internal model -> external DTO

즉 “형태를 바꾸는 일”까지만 한다.

2. 비즈니스 규칙은 매퍼에 넣지 않는다

다음은 매퍼 책임이 아니다.

  • 상태 전이 결정
  • 권한 판정
  • 에러 코드 결정
  • 정책 선택
  • 유효성 최종 판정
  • 도메인 invariant 강제의 주 책임

단, 단순한 정규화/포맷 수준의 보조 변환은 허용될 수 있다.

3. 매퍼에서 repository / external API / service 호출 금지

매퍼는 pure mapping에 가깝게 유지한다.

금지:

  • DB 조회
  • 외부 API 호출
  • 다른 aggregate 탐색을 위한 repository 호출
  • 메시지 발행
  • 파일/네트워크 접근

매핑에 필요한 부가 데이터가 있으면 호출자가 먼저 준비해서 매퍼에 전달한다.

4. 경계마다 매퍼를 분리한다

다음 경계를 하나의 매퍼로 섞지 않는다.

  • web request/response 변환
  • persistence entity 변환
  • external integration 변환

예:

  • UserWebMapper
  • UserPersistenceMapper
  • VaultApiMapper

처럼 경계별로 분리한다.

5. DTO / Domain / Entity를 직접 섞지 않는다

하나의 매퍼가 다음을 동시에 다루며 의미를 섞지 않게 한다.

  • request DTO
  • domain
  • JPA entity
  • external API payload

필요하면 경계별로 mapper를 여러 개 둔다.

6. 매퍼는 기술 세부보다 구조적 대응 관계를 표현

좋은 매퍼는 아래를 명확하게 보여야 한다.

  • 어떤 source를 어떤 target으로 바꾸는가
  • 어떤 필드가 대응되는가
  • 어떤 값이 누락되거나 기본값 처리되는가

반대로 아래는 매퍼에 새기지 않는다.

  • HTTP 상태 코드
  • DB 트랜잭션
  • serialization 포맷 정책
  • retry/fallback 정책

7. update mapping은 명시적으로만

기존 target을 수정하는 매핑은 “새로 생성하는 매핑”과 구분한다.

기본:

  • create mapping
  • update mapping

을 별도 메서드로 둔다.

기존 객체 갱신은 side effect가 있으므로 이름과 계약을 분명히 한다.

8. null / empty / default 처리 정책을 숨기지 않는다

매퍼는 다음을 명확히 해야 한다.

  • null source를 허용하는가
  • null field를 무시하는가
  • null이면 target을 덮어쓰는가
  • empty collection을 그대로 넣는가
  • 기본값을 넣는가

정책이 중요하면 호출자/standard 문서에서 먼저 정하고 매퍼에 일관되게 반영한다.

9. mapper는 가능한 한 결정적이고 테스트 가능해야 한다

같은 입력이면 같은 결과가 나와야 한다.

지양:

  • 현재 시각 생성
  • 랜덤 값 생성
  • 환경값 조회
  • thread-local/MDC 접근

정말 필요하면 호출자가 값을 주입한다.

10. request binding과 도메인 생성은 구분한다

Spring DataBinder/Web binding이 request를 객체로 바꿔 주더라도, 그 객체를 domain으로 승격하는 과정은 별도 매퍼/assembler 또는 factory에서 통제한다.

즉:

  • web binding = 입력 수집
  • mapper = 구조 변환
  • domain factory/value object = 의미/불변식 부여

11. persistence mapper는 DB nullable/오염 상태를 domain으로 직접 흘리지 않는다

entity -> domain 매핑에서는:

  • nullable column
  • legacy 값
  • 잘못된 저장 데이터 를 명시적으로 처리한다.

필요하면 예외를 던지거나 복원 규칙을 적용하지만, 조용히 의미를 바꾸지 않는다.

12. external integration mapper는 wire format을 코어에 새기지 않는다

외부 API JSON/XML/HTTP payload 구조는 integration 전용 mapper에서 소화한다. core/application/domain은 외부 wire format 세부를 몰라야 한다.

13. 자동 매핑 도구를 써도 책임은 그대로

MapStruct 같은 도구를 써도 아래 원칙은 변하지 않는다.

  • 비즈니스 로직을 매퍼에 넣지 않는다
  • update/create를 구분한다
  • 경계별 매퍼를 분리한다
  • null/default 정책을 숨기지 않는다

도구는 구현 보조일 뿐 설계 기준을 대체하지 않는다.

프로젝트 기준 요약

  • 매퍼는 번역만 한다
  • 비즈니스 정책/DB 조회/외부 호출 금지
  • web / persistence / integration 매퍼 분리
  • create mapping과 update mapping 분리
  • null/default 정책 명시
  • 결정적이고 테스트 가능하게 유지
  • domain 의미 부여와 매핑을 혼동하지 않음