Files

12 KiB

API Controller 기준

1. 목적

이 문서는 Spring MVC 기반 API controller의 책임, 위치, 작성 방식, 금지사항을 정의한다.

이 문서의 목표는 다음과 같다.

  • controller를 HTTP boundary 역할에 집중시킨다.
  • request parsing / validation / authentication context extraction / response shaping의 위치를 명확히 한다.
  • business rule, transaction, persistence access, external integration이 controller로 새어 들어오지 않게 한다.
  • API controller가 프로젝트 전체에서 일관된 구조를 가지게 한다.

2. 근거 수준

  • Official: Spring Framework / Spring Boot 공식 문서에서 직접 확인되는 내용
  • Official + Practice: 공식 문서의 확장 지점 위에 일반적인 실무 API 설계 원칙을 결합한 내용
  • Project Recommendation: 이 프로젝트 구조와 운영 방식에 맞춘 규칙

3. 기본 원칙

3.1 API endpoint는 기본적으로 @RestController를 사용한다

Spring 공식 문서 기준으로 @RestController는 @Controller와 @ResponseBody를 결합한 annotation이며, @RequestMapping 메서드가 기본적으로 response body semantics를 가진다. JSON/HTTP body를 반환하는 API controller의 기본 선택지는 @RestController다. HTML view rendering이 목적일 때만 @Controller를 사용한다.

프로젝트 규칙:

  • REST API endpoint의 기본값은 @RestController
  • 서버사이드 템플릿 렌더링 등 view name 반환이 목적일 때만 @Controller
  • 같은 controller 안에 API 응답과 view 렌더링을 섞지 않는다

3.2 controller는 HTTP boundary translator다

Spring MVC는 controller 메서드에서 request mapping, request input, exception handling, return value rendering을 담당할 수 있게 해 준다. 그러나 이 프로젝트에서 controller의 핵심 책임은 “HTTP 요청을 애플리케이션 입력으로 번역하고, 애플리케이션 결과를 HTTP 응답으로 번역하는 것”이다.

프로젝트 규칙:

  • controller는 request를 해석한다
  • 필요한 인증 주체/식별자/입력을 추출한다
  • application service / use case를 호출한다
  • 응답 DTO 또는 ApiResult로 응답을 만든다

그 외의 핵심 business decision은 controller의 기본 책임이 아니다.

3.3 controller는 얇게 유지한다

이 항목은 Official + Practice + Project Recommendation 이다.

프로젝트 규칙:

  • controller는 가능한 한 짧고 예측 가능해야 한다
  • HTTP concern 외의 분기와 규칙은 application/domain으로 이동한다
  • controller 하나가 여러 repository, external client, transaction concern을 직접 조립하지 않는다
  • “request 파싱 → use case 호출 → response 반환” 흐름이 한눈에 보여야 한다

4. 매핑 표준

4.1 class-level base path + method-level endpoint를 사용한다

Spring 공식 문서 기준으로 @RequestMapping은 class level에서 shared mapping을, method level에서 구체 endpoint mapping을 표현할 수 있다. 또한 대부분의 controller method는 모든 HTTP method를 받는 일반 @RequestMapping보다 @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping 같은 method-specific shortcut을 쓰는 것이 자연스럽다. 같은 element에 여러 @RequestMapping 계열을 함께 두면 첫 번째만 사용되고 warning이 남는다.

프로젝트 규칙:

  • controller 클래스에는 resource base path를 둔다
  • endpoint 메서드에는 HTTP method-specific mapping을 사용한다
  • endpoint 메서드에 일반 @RequestMapping을 남발하지 않는다
  • 한 메서드에 중복 mapping annotation을 두지 않는다

4.2 매핑 조건은 필요한 만큼만 사용한다

Spring은 mapping에 URL, HTTP method, params, headers, media types를 모두 조건으로 줄 수 있다. 하지만 이 프로젝트에서는 매핑 조건을 너무 많이 걸어 endpoint를 읽기 어렵게 만드는 것을 지양한다. 꼭 필요한 경우에만 consumes, produces, params, headers를 사용한다.

프로젝트 규칙:

  • 기본은 path + HTTP method
  • content-type이 중요한 endpoint만 consumes
  • response media type이 실제로 갈리는 endpoint만 produces
  • params/headers 조건은 routing necessity가 분명할 때만 사용

4.3 endpoint path는 리소스 중심으로 둔다

이 항목은 주로 Practice + Project Recommendation 이다.

프로젝트 규칙:

  • path는 동사보다 리소스 중심으로 설계한다
  • action semantics는 가능한 한 HTTP method로 표현한다
  • 도메인적으로 특별한 command endpoint가 필요하면 예외적으로 명시적 action path를 사용할 수 있다
  • controller 이름, class path, method path가 함께 읽혔을 때 자원이 자연스럽게 보이게 한다

5. 메서드 시그니처 표준

5.1 인자는 명시적으로 바인딩한다

Spring MVC controller method는 매우 다양한 인자를 지원한다. HttpServletRequest, HttpServletResponse, WebRequest, HttpSession, @PathVariable, @RequestParam, @RequestHeader, @ModelAttribute, @RequestBody 등 여러 타입과 annotation을 사용할 수 있다. 그러나 지원된다고 해서 아무 인자나 다 쓰는 방향을 기본값으로 두지 않는다.

프로젝트 규칙:

  • path 값은 @PathVariable
  • query 값은 @RequestParam
  • header 값은 @RequestHeader
  • JSON body는 @RequestBody
  • form/query binding object는 @ModelAttribute
  • 각 입력의 출처가 메서드 시그니처에 드러나야 한다

5.2 request DTO를 명시적으로 사용한다

Spring 공식 문서 기준으로 @ModelAttribute는 request parameters, URI path variables, headers를 model object에 바인딩하며, 보안상 웹 바인딩 전용 객체를 쓰거나 constructor binding only를 고려하고, property binding이 필요하면 allowedFields를 제한하라고 권장한다. 즉, 웹 입력용 객체 설계는 신중해야 한다.

프로젝트 규칙:

  • JSON body는 전용 request DTO에 받는다
  • form/query binding도 가능하면 전용 request model로 받는다
  • entity/domain object를 직접 @RequestBody / @ModelAttribute 대상으로 쓰지 않는다
  • request DTO는 transport model이지 domain model이 아니다

5.3 Servlet API 의존은 정말 필요할 때만 허용한다

Spring MVC는 HttpServletRequest, HttpServletResponse, WebRequest 등 직접적인 request/response 접근을 지원한다. 하지만 API controller의 기본 시그니처는 annotation 기반 인자 바인딩으로 충분해야 한다.

프로젝트 규칙:

  • request body, path, query, header는 annotation 기반 인자 바인딩을 우선한다
  • HttpServletRequest / HttpServletResponse는 다음처럼 정말 필요한 경우에만 사용한다
  • request attribute 접근
  • low-level header 처리
  • cookie 직접 제어
  • file streaming / low-level response control
  • 단순 입력 추출 때문에 Servlet API를 들고 오지 않는다

5.4 변환과 포맷팅은 ad-hoc parsing보다 binder/converter를 우선 검토한다

Spring 공식 문서 기준으로 @InitBinder나 전역 MVC config를 통해 Converter, Formatter, PropertyEditor 등을 등록해 타입 변환과 formatting을 구성할 수 있다. controller 안에서 문자열 파싱 로직을 계속 반복하는 것보다 framework extension point를 쓰는 편이 낫다.

프로젝트 규칙:

  • 동일한 문자열 → 타입 변환이 반복되면 converter/formatter를 검토한다
  • web binding 전용 커스터마이징은 @InitBinder 또는 전역 conversion 설정으로 뺀다
  • controller 본문에 파싱 로직을 반복해서 쓰지 않는다

6. 반환값 표준

6.1 기본 반환은 body 중심이다

Spring MVC는 @ResponseBody, ResponseEntity, HttpHeaders, ProblemDetail, String view name 등 다양한 반환형을 지원한다. API controller에서는 body 중심 반환이 기본이다. @RestController는 class level @ResponseBody semantics를 제공한다.

프로젝트 규칙:

  • 일반 API 성공 응답은 ApiResult 또는 프로젝트 표준 response DTO를 반환한다
  • view name 반환은 API controller에서 사용하지 않는다
  • Map<String, Object> 같은 임시 응답은 승인 후보 코드에서 지양한다

6.2 ResponseEntity는 “정말 HTTP 응답을 제어해야 할 때” 사용한다

Spring 공식 문서 기준으로 ResponseEntity는 전체 응답, 즉 HTTP status, headers, body를 함께 지정하는 반환형이다. 따라서 모든 endpoint에서 습관적으로 ResponseEntity를 쓸 필요는 없고, HTTP 응답 제어가 필요한 경우에 의미가 있다.

프로젝트 규칙:

  • 기본 성공 200 응답이고 header 제어가 없다면 굳이 ResponseEntity를 강제하지 않는다
  • 다음 경우에는 ResponseEntity를 사용한다
  • 201 Created + Location
  • 204 No Content
  • header 제어
  • 캐시/조건부 응답
  • 파일 다운로드
  • endpoint별로 status가 의미 있게 달라지는 경우

6.3 응답 body 공통화는 controller보다 advice에서 해결할 수 있다

Spring의 ResponseBodyAdvice는 @ResponseBody 또는 ResponseEntity controller method의 body가 HttpMessageConverter로 쓰이기 직전에 응답을 커스터마이징할 수 있고, @ControllerAdvice로 등록하면 자동 적용된다. 따라서 전역 ApiResult 래핑이나 공통 필드 보강은 controller 메서드마다 반복하지 않고 advice에 둘 수 있다.

프로젝트 규칙:

  • 응답 envelope 공통화 정책이 있으면 controller 반복보다 advice를 우선 검토한다
  • 다만 controller가 이미 명시적으로 ApiResult를 반환하는 프로젝트라면 이중 래핑을 피한다

7. 예외, 검증, 인증 경계

7.1 controller는 예외를 직접 처리하는 기본 위치가 아니다

Spring 공식 문서 기준으로 @ExceptionHandler, @InitBinder, @ModelAttribute는 local controller에도 둘 수 있고, @ControllerAdvice / @RestControllerAdvice로 전역 적용도 가능하다. 전역 @ExceptionHandler는 local handler 뒤에 적용된다.

프로젝트 규칙:

  • 일반적인 API 예외 처리는 @RestControllerAdvice에 둔다
  • controller 메서드 안에서 try-catch로 공통 예외 응답을 반복해서 만들지 않는다
  • controller local @ExceptionHandler는 endpoint-local 정책이 정말 필요할 때만 사용한다

7.2 controller의 검증 책임은 request boundary까지다

이 항목은 앞서 정리한 validation 문서와 연결되는 Official + Practice + Project Recommendation 이다.

프로젝트 규칙:

  • controller는 request DTO / scalar input의 구조 검증을 트리거한다
  • business rule 검증, 상태 조회 기반 검증, 불변식 보장은 application/domain에서 담당한다
  • controller validation 통과를 domain correctness의 보장으로 간주하지 않는다

7.3 인증 주체 접근은 명시적이고 제한적으로 한다

Spring MVC는 다양한 method argument를 허용하므로 인증 객체도 여러 방식으로 전달될 수 있다. 이 프로젝트에서는 인증 객체 접근을 controller 시그니처에서 명시적으로 드러내되, security implementation 세부사항이 controller 전체에 번지지 않게 한다.

프로젝트 규칙:

  • 인증 주체는 가능한 한 전용 principal / auth object / argument resolver 결과로 받는다
  • controller가 security framework의 저수준 타입에 과도하게 묶이지 않게 한다
  • 인증 객체 접근 표준은 이후 Authentication Object Access Standard 문서에서 더 구체화한다

8. 의존성 및 금지 규칙

이 항목은 주로 Project Recommendation 이다.

프로젝트 규칙:

  • controller는 repository를 직접 호출하지 않는다
  • controller는 EntityManager, JDBC template, external client를 직접 조립하지 않는다
  • controller에 @Transactional을 기본 금지한다
  • controller는 domain entity를 외부 응답 모델로 직접 반환하지 않는다
  • controller에서 event 발행, retry, async orchestration을 직접 수행하지 않는다
  • controller 메서드가 “HTTP boundary 번역” 이상으로 커지기 시작하면 application service / mapper / advisor / resolver 분리를 검토한다

9. 체크리스트

다음 질문에 “예”로 답할 수 있어야 한다.

  • 이 클래스는 REST API endpoint이므로 @RestController가 자연스러운가?
  • class-level path와 method-level HTTP mapping이 명확한가?
  • 입력 출처가 메서드 시그니처에서 드러나는가?
  • request DTO와 domain/entity가 분리되어 있는가?
  • controller가 repository/transaction/business rule을 직접 품고 있지 않은가?
  • 응답 형식이 프로젝트 표준(ApiResult 등)에 맞는가?
  • 예외 처리와 공통 응답 보강을 advice 쪽으로 밀어냈는가?