API 요청 모델을 핵심 도메인에서 분리하라
프레젠테이션 경계에서 외부 요청을 비즈니스가 소유한 값으로 바꿔 API는 안쪽을 의존하고 코어는 바깥을 모르도록 만드는 방법입니다.
출처 및 AI 안내: 이 글은 제미니의 개발실무 유튜브를 기반으로 작성되었습니다.
gpt-5.6-sol모델을 사용해 생성·편집했습니다.
애플리케이션을 코어 모듈과 어드민·사용자 API로 나누면 바로 질문이 생긴다. 컨트롤러가 받은 요청 본문을 그다음 계층에 어떻게 넘길 것인가?
API 요청 객체를 코어 서비스에 그대로 전달하면 편하다. 하지만 코어가 API 모듈을 의존하게 된다. API가 이미 코어를 의존한다면 순환 구조가 된다. 프레젠테이션 계층이 외부 모양을 받고 비즈니스가 소유한 객체로 변환해 안쪽으로 넘기는 편이 의존 방향을 한쪽으로 유지한다.
변환도 경계의 일이다
주문 엔드포인트가 NewOrderRequest를 받는다고 해보자. 이 타입은 HTTP 계약을 표현하므로 API에 속한다. 비즈니스 흐름을 호출하기 전에 컨트롤러나 인접한 프레젠테이션 코드가 이를 도메인의 NewOrder로 바꾼다.
요청 필드를 모두 꺼내 서비스 파라미터로 하나씩 전달할 수도 있다. 모듈 의존은 피하지만 필드가 많아질수록 호출이 복잡해진다. 비즈니스가 소유한 입력 객체를 만들면 전송 타입을 새게 하지 않으면서도 하나의 의미 있는 요청으로 전달할 수 있다.
각 모듈이 아는 범위는 분명해진다.
- API는 요청 모델과 도메인 입력을 안다.
- 코어는 도메인 입력만 안다.
- 코어는 API를 임포트하지 않는다.
값도 안쪽으로 흐르고 의존성도 안쪽을 향한다.
필드가 비슷해도 같은 타입은 아니다
처음에는 요청 모델과 도메인 입력의 모양이 거의 같을 수 있다. 그래도 둘은 변경 이유가 다르다. 요청은 외부 스펙을 따르고, 도메인 객체는 비즈니스가 지키려는 개념을 따른다.
결제 요청을 예로 들면 차이가 보인다. 외부 API는 여러 금액 필드와 게이트웨이 속성을 원시 값으로 받을 수 있다. 변환 과정에서는 금액들을 의미 있는 객체로 묶고 결제 속성도 도메인이 소유한 값으로 만들 수 있다. 어떤 값이 한 덩어리인지, 안쪽 모델이 어떤 조건을 표현해야 하는지 고민할 지점이 바로 이 경계다.
두 표현이 계속 같고 분리에서 얻는 것이 없다면 절차를 줄일 수도 있다. 모든 DTO를 복제하자는 규칙이 아니다. 변환이 번거롭다는 이유만으로 외부 계약을 코어의 언어로 만들지 말자는 것이다.
외부 변경은 가장자리에서 멈춘다
API 필드는 이름이 바뀌고, 추가되며, 순서가 달라질 수 있다. 비즈니스 의미가 그대로라면 요청 타입과 변환 코드가 그 변화를 흡수해야 한다. 코어는 같은 의미 있는 입력을 계속 받으면 된다.
반대로 도메인이 개념을 더 정교하게 나눠도 외부 요청이 내부 모양을 그대로 따라갈 필요는 없다. 경계가 한 방향으로 번역하므로 두 모델은 각자의 이유로 변할 수 있다.
모듈 조립도 이해하기 쉬워진다. 어드민 API와 사용자 API가 서로 다른 요청 계약을 가져도 같은 코어 기능을 호출할 수 있다. 각 API의 DTO를 다른 API에 공개할 필요가 없고, 코어가 전송 세부사항의 집합소가 되지도 않는다.
요청 객체는 요청을 받는 인터페이스의 것이다. 비즈니스 객체는 그 값을 가지고 행위하는 코드의 것이다. 둘 사이의 변환은 쓸데없는 매핑이 아니라 외부 스펙이 애플리케이션 중심을 정의하지 못하게 막는 경계 작업이다.