#9 - Controller 코딩 레벨 에서 순서 및 설명
중년개발자
@loxo
4일 전
Controller 실무 가이드
Spring Boot에서 Controller를 실제로 어떻게 작성하는가
어노테이션, 클래스, Response 설계를 코딩 순서대로 정리한다
이 문서의 목적
Controller를 설명할 때 가장 많이 나오는 말은 이것이다.
“Controller는 얇아야 한다”
하지만 실무에서는 곧바로 이런 질문이 나온다.
그래서 뭐를 쓰고, 뭐를 어디까지 작성하라는 건데?
이 문서는:
- Controller에서 반드시 알아야 할 클래스와 어노테이션을
- 실제 코딩 순서에 맞게 설명하고
- 특히 Response 설계를 실무 기준으로 정리한다.
1. Controller 작성 순서 (이 순서를 지키면 흔들리지 않는다)
Controller는 보통 아래 순서로 작성한다.
- Controller 선언
- Request 매핑 정의
- 요청 데이터 바인딩
- 검증 선언
- Service 위임
- Response 반환
이 순서를 기준으로 하나씩 정리한다.
2. Controller 선언에 필요한 어노테이션
@RestController
@RestController의미:
- 이 클래스는 HTTP API의 입구다
- 모든 메서드는 기본적으로 JSON Response를 반환한다
포함 관계:
@Controller@ResponseBody
👉 API 서버에서는 @Controller 대신 @RestController만 사용한다.
@RequiredArgsConstructor
@RequiredArgsConstructor의미:
- final 필드에 대해 생성자를 자동 생성
- Controller에서 Service 주입을 강제적으로 명확하게 만든다
👉 필드 주입(@Autowired)은 사용하지 않는다.
3. Request 매핑 어노테이션
@PostMapping / @GetMapping / @PutMapping / @DeleteMapping
@PostMapping("/orders")의미:
- HTTP Method + URL을 하나의 유스케이스로 고정
실무 기준:
- 하나의 Controller 메서드 = 하나의 유스케이스
- 여러 Service를 조합하지 않는다
4. 요청 데이터 바인딩
@RequestBody
public ResponseEntity<Void> create(@RequestBody CreateOrderRequest request)의미:
- HTTP Body(JSON)를 Java 객체로 변환
중요한 기준:
- Entity를 직접 받지 않는다
- 반드시 전용 Request DTO를 사용한다
@PathVariable / @RequestParam
@GetMapping("/orders/{id}")
public ResponseEntity<OrderResponse> get(@PathVariable Long id)역할:
- URL 자체가 갖는 정보를 명시적으로 드러낸다
5. 검증 어노테이션: 프론트를 믿지 않겠다는 선언
@Valid
public ResponseEntity<Void> create(
@Valid @RequestBody CreateOrderRequest request
)의미:
- 이 요청은 검증을 통과해야만 시스템 안으로 들어온다
자주 쓰는 검증 어노테이션:
| 어노테이션 | 역할 |
|---|---|
| @NotNull | null 금지 |
| @NotBlank | 빈 문자열 금지 |
| @Positive | 양수 |
| 이메일 형식 |
👉 검증은 Controller에서 끝내고, Service는 전제를 믿는다.
아래는 실제로 사용하는 @Valid Request DTO 예시다.
public record CreateOrderRequest(
@NotNull(message = "상품 ID는 필수입니다")
Long productId,
@Positive(message = "수량은 1 이상이어야 합니다")
int quantity,
@NotBlank(message = "수령인 이름은 필수입니다")
String receiverName,
@Email(message = "이메일 형식이 올바르지 않습니다")
String receiverEmail
) {}이 Request DTO의 특징은 다음과 같다.
- 검증 규칙이 데이터 옆에 붙어 있다
- if 문이 Controller에 등장하지 않는다
- "이 값이 유효한가"라는 질문이 Service로 내려가지 않는다
Request DTO는 요청을 신뢰 가능한 형태로 고정하는 역할만 맡는다.
6. Service 호출 기준 (가장 중요한 원칙)
orderService.create(request);Controller에서 Service 호출에 대한 기준은 단순하다.
- 한 요청 → 한 Service 메서드 호출
- 여러 규칙이 필요하면 Service를 새로 만든다
Controller는 지휘하지 않는다.
Controller는 위임만 한다.
7. ResponseEntity: 응답의 표준 컨테이너
왜 ResponseEntity를 쓰는가
return ResponseEntity.ok(response);ResponseEntity는 다음을 한 번에 표현한다.
- HTTP Status
- Response Body
- Header
👉 "응답의 의도를 코드로 드러내는 도구"다.
자주 사용하는 Response 패턴
// 200 OK
ResponseEntity.ok(body)
// 201 Created
ResponseEntity.status(HttpStatus.CREATED).build()
// 204 No Content
ResponseEntity.noContent().build()
// 400 / 404 / 500 은 예외로 처리실무 팁:
- 성공 응답만 Controller에서 만든다
- 실패 응답은 예외 + @RestControllerAdvice로 통일한다
8. Response 설계의 핵심 원칙
Response는 항상 일관돼야 한다
Response는 클라이언트와의 계약이다.
그래서 실무에서는 보통 다음 중 하나를 선택한다.
패턴 1. HTTP Status + Body 최소화
{
"id": 1
}패턴 2. 공통 Response Wrapper
{
"success": true,
"data": {...}
}👉 팀 내에서 반드시 하나로 통일해야 한다.
9. DTO → Response DTO 변환 위치
Order order = orderService.get(id);
return ResponseEntity.ok(OrderResponse.from(order));허용 기준:
- 의미 변경 ❌
- 판단 ❌
- 표현 변환 ⭕
Controller는 보이게만 바꾼다.
10. Controller 코드 예시 (정석 형태)
@RestController
@RequiredArgsConstructor
class OrderController {
private final OrderService orderService;
@PostMapping("/orders")
public ResponseEntity<Void> create(
@Valid @RequestBody CreateOrderRequest request
) {
orderService.create(request);
return ResponseEntity.status(HttpStatus.CREATED).build();
}
@GetMapping("/orders/{id}")
public ResponseEntity<OrderResponse> get(@PathVariable Long id) {
Order order = orderService.get(id);
return ResponseEntity.ok(OrderResponse.from(order));
}
}이 Controller는:
- 의심하고
- 위임하고
- 전달한다
그 이상도, 그 이하도 아니다.
11. 마지막 정리
Controller를 잘 작성했다는 신호는 이것이다.
- 읽자마자 역할이 보인다
- 테스트가 쉽다
- 비즈니스 질문이 떠오르지 않는다
Controller는 코드를 쓰는 곳이 아니라, 경계를 긋는 곳이다.
이 기준을 지키면, Controller는 더 이상 어려운 레이어가 아니다.