# Request / Response DTO 기준 ## 1. 목적 이 문서는 API 요청/응답에 사용하는 DTO의 역할, 위치, 설계 규칙을 정의한다. 이 문서의 목표는 다음과 같다. - request DTO와 response DTO를 명확히 분리한다. - web transport model과 domain/application model을 섞지 않는다. - controller 바인딩 모델의 보안과 변경 가능성을 통제한다. - 응답 포맷을 domain/entity 구조가 아니라 API 계약 중심으로 설계한다. ## 2. 근거 수준 - Official: Spring Framework 공식 문서에서 직접 확인되는 내용 - Official + Practice: 공식 문서의 확장 지점 위에 일반적인 실무 API 설계 원칙을 결합한 내용 - Project Recommendation: 이 프로젝트 구조와 운영 방식에 맞춘 규칙 ## 3. 기본 원칙 ### 3.1 DTO는 transport model이다 Spring MVC는 @RequestBody와 @ModelAttribute를 통해 웹 입력을 객체로 바인딩하고, @ResponseBody/ResponseEntity를 통해 객체를 응답으로 직렬화한다. 이 프로젝트에서 DTO는 그 바운더리에서만 쓰는 transport model로 정의한다. DTO는 HTTP 요청/응답 계약을 표현하는 객체이지, domain/entity/application command 자체가 아니다. 프로젝트 규칙: - request DTO는 HTTP 입력 계약을 표현한다 - response DTO는 HTTP 출력 계약을 표현한다 - DTO는 domain/entity/persistence model을 그대로 노출하는 수단이 아니다 ### 3.2 request DTO와 response DTO를 분리한다 Spring 공식 문서는 @ModelAttribute 대상에 대해 웹 바인딩 전용 객체 사용을 권장한다. 이 원칙을 request/response 전체로 확장하면, 읽기 모델과 쓰기 모델을 분리하는 것이 안전하고 유지보수성이 높다. 프로젝트 규칙: - 하나의 DTO를 request와 response에 동시에 재사용하지 않는다 - create/update 입력 DTO와 조회 응답 DTO를 분리한다 - “필드가 비슷하니까 같은 DTO”를 기본값으로 두지 않는다 ### 3.3 entity와 DTO를 섞지 않는다 Spring은 @ModelAttribute 바인딩 모델 설계 시 보안을 고려해 전용 모델 객체 또는 constructor binding only를 권장한다. 이는 웹 바인딩 대상을 domain/entity와 분리하라는 실무 방향과 맞닿아 있다. 프로젝트 규칙: - entity를 @RequestBody 대상으로 쓰지 않는다 - entity를 @ModelAttribute 대상으로 쓰지 않는다 - entity를 API response body로 직접 반환하지 않는다 - domain object를 외부 계약 모델로 직접 노출하지 않는다 ## 4. Request DTO 표준 ### 4.1 JSON body는 전용 request DTO로 받는다 Spring 공식 문서 기준으로 @RequestBody는 요청 본문을 HttpMessageConverter로 객체에 역직렬화한다. 따라서 JSON API 입력은 전용 request DTO에 받는 것이 기본이다. 프로젝트 규칙: - JSON body는 전용 request DTO에 받는다 - request DTO는 controller boundary에서만 사용한다 - request DTO를 그대로 domain/service 내부에 전파하지 않는다 - controller 또는 mapper에서 application/domain 입력으로 변환한다 ### 4.2 form/query/path 기반 입력도 web 전용 모델로 받는다 Spring 공식 문서 기준으로 @ModelAttribute는 request parameters, path variables, headers를 모델 객체에 바인딩할 수 있다. Spring은 이 모델을 웹 바인딩 전용으로 설계하거나 constructor binding only를 권장한다. 프로젝트 규칙: - query/form/multipart 조합 입력도 전용 request model 사용을 우선 검토한다 - @ModelAttribute 대상은 web binding 전용 객체로 제한한다 - setter/property binding을 허용해야 한다면 바인딩 가능한 필드를 의식적으로 통제한다 ### 4.3 request DTO는 입력 계약만 표현한다 프로젝트 규칙: - request DTO에는 HTTP 입력 필드만 둔다 - 서버 내부 계산값, 조회 결과, 인증 결과, 저장 전용 필드를 넣지 않는다 - “나중에 응답에도 쓸 수 있으니 미리 넣어두기”를 금지한다 이 규칙은 Practice + Project Recommendation 이다. ### 4.4 request DTO는 validation 경계를 드러낼 수 있어야 한다 Spring MVC는 @RequestBody/@ModelAttribute 입력에 @Valid/@Validated 검증을 적용할 수 있다. 따라서 request DTO는 request shape validation이 걸릴 자리라는 점이 명확해야 한다. 프로젝트 규칙: - request DTO에는 입력 형식 검증에 필요한 constraint를 둘 수 있다 - business rule을 request DTO constraint에 과도하게 숨기지 않는다 - request DTO validation 통과를 domain correctness의 보장으로 간주하지 않는다 ### 4.5 request DTO는 API 변경에 안전해야 한다 Spring이 전용 바인딩 객체와 constructor binding을 권장하는 이유는 바인딩 범위를 명시적으로 통제하기 위함이다. 이 프로젝트에서는 이를 API 변경 안전성 원칙으로 확장한다. 프로젝트 규칙: - request DTO는 허용된 입력만 명시적으로 받는다 - 무분별한 setter/property 확장을 지양한다 - 클라이언트가 보내면 안 되는 필드가 우연히 열리지 않도록 설계한다 ## 5. Response DTO 표준 ### 5.1 응답은 response DTO 또는 표준 envelope로 반환한다 Spring MVC는 @ResponseBody와 ResponseEntity를 통해 객체를 응답으로 직렬화한다. 이 프로젝트에서는 응답 body를 domain/entity가 아니라 response DTO 또는 ApiResult 같은 표준 envelope로 반환한다. 프로젝트 규칙: - 응답 본문은 response DTO 또는 ApiResult로 표현한다 - entity, aggregate, persistence projection을 그대로 반환하지 않는다 - Map 기반 임시 응답은 승인 후보 코드에서 지양한다 ### 5.2 response DTO는 외부 계약 중심으로 설계한다 프로젝트 규칙: - response DTO는 클라이언트가 실제로 필요한 필드만 포함한다 - 내부 식별자, 상태값, 구현 세부사항을 무분별하게 노출하지 않는다 - domain 모델 필드 구조를 그대로 따라가는 것을 기본값으로 두지 않는다 이 항목은 Practice + Project Recommendation 이다. ### 5.3 request DTO와 response DTO를 상호 재사용하지 않는다 프로젝트 규칙: - 생성 요청 DTO를 조회 응답 DTO로 재사용하지 않는다 - 응답에서만 필요한 필드와 요청에서만 필요한 필드를 분리한다 - “대칭 구조”를 위해 무의미한 필드를 추가하지 않는다 ## 6. 매핑 규칙 ### 6.1 DTO ↔ domain/application 변환은 명시적으로 한다 Spring의 바인딩/직렬화는 controller boundary까지의 편의를 제공하지만, DTO를 domain/application 모델로 자동 동일시하지는 않는다. 따라서 DTO ↔ 내부 모델 변환은 명시적으로 관리한다. 이 규칙은 Spring의 boundary-oriented programming model에 대한 Project Recommendation 이다. 프로젝트 규칙: - request DTO → application input 변환은 controller 또는 전용 mapper가 담당한다 - domain/application result → response DTO 변환도 명시적으로 수행한다 - mapper는 transport concern과 domain concern을 구분해서 작성한다 ### 6.2 controller가 DTO를 내부 모델처럼 들고 다니지 않는다 프로젝트 규칙: - use case 메서드 시그니처에 request DTO를 그대로 넘기지 않는다 - application/domain이 response DTO를 직접 생성하지 않는다 - DTO는 presentation 경계 안에서 생성/소비를 끝내는 쪽을 기본값으로 둔다 ## 7. 바인딩 보안 규칙 ### 7.1 @ModelAttribute 대상은 특히 더 조심한다 Spring 공식 문서는 @ModelAttribute에서 전용 웹 바인딩 객체 또는 constructor binding only를 권장하고, property binding이 필요하면 allowedFields로 제한하라고 권장한다. 프로젝트 규칙: - @ModelAttribute 대상은 전용 DTO로 둔다 - mutable property binding을 열어야 한다면 바인딩 가능한 필드를 통제한다 - 도메인 객체나 영속 객체를 @ModelAttribute로 받지 않는다 ### 7.2 request DTO에 서버 소유 필드를 두지 않는다 프로젝트 규칙: - userId, role, status, createdAt, updatedAt 같은 서버 소유 값은 request DTO에 기본적으로 두지 않는다 - 필요한 경우에도 클라이언트 입력값과 서버 결정값의 출처를 명확히 분리한다 이 항목은 Practice + Project Recommendation 이다. ## 8. 반환/응답 제어 규칙 ### 8.1 ResponseEntity는 전체 응답 제어가 필요할 때 사용한다 Spring 공식 문서 기준으로 ResponseEntity는 status, headers, body를 함께 지정하는 전체 응답 표현이다. 따라서 단순 성공 200 응답에는 항상 필요하지 않다. 프로젝트 규칙: - 기본 성공 응답은 DTO 또는 ApiResult 반환으로 충분하면 그렇게 한다 - 다음 경우에는 ResponseEntity를 사용한다 - 201 Created + Location - 204 No Content - custom header - 캐시/조건부 응답 - 파일 다운로드/streaming 등 HTTP 제어가 중요한 경우 ### 8.2 공통 envelope는 DTO 설계와 충돌하지 않게 한다 Spring의 ResponseBodyAdvice는 응답 body 공통 가공 지점이다. 따라서 ApiResult 같은 공통 응답 envelope를 쓴다면 response DTO 설계와 중복/충돌이 없도록 해야 한다. 프로젝트 규칙: - response DTO는 business payload를 표현한다 - ApiResult는 공통 wrapper 역할에 집중한다 - response DTO 안에 다시 status/code/message 같은 공통 wrapper 성격 필드를 중복으로 넣지 않는다 ## 9. 금지 규칙 다음은 기본 금지다. - entity를 request DTO로 사용 - entity를 response DTO로 직접 노출 - request/response DTO를 하나로 합치기 - request DTO를 application/domain 메서드 시그니처에 그대로 넘기기 - response DTO 생성을 domain/application 안에서 직접 수행하기 - 임시 Map 응답 남발 - @ModelAttribute 대상에 무분별한 property binding 열기 - DTO에 서버 소유 필드를 무심코 포함시키기 ## 10. 체크리스트 다음 질문에 “예”로 답할 수 있어야 한다. - 이 객체는 request용인가, response용인가가 명확한가? - web transport model과 domain/entity가 분리되어 있는가? - JSON body라면 @RequestBody 전용 DTO인가? - form/query binding이라면 @ModelAttribute 전용 DTO인가? - response DTO가 외부 계약 중심으로 설계되어 있는가? - DTO가 서버 내부 정책/엔티티 구조를 그대로 노출하지 않는가? - DTO ↔ 내부 모델 변환 위치가 명확한가?