컨트롤러 작성 가이드
기본 원칙
- Controller는 요청/응답 처리만 담당한다. 비즈니스 로직을 포함하지 않는다.
@RequiredArgsConstructor로 생성자 주입을 사용한다.@Slf4j로 로그를 선언한다.- Entity를 직접 반환하지 않는다. 반드시 VO(Res)로 변환 후 반환한다.
화면 컨트롤러 (Thymeleaf)
@Controller
@RequestMapping("/notes")
@RequiredArgsConstructor
@Slf4j
public class NotesController {
private final PostService postService;
@GetMapping
public String list(@RequestParam(defaultValue = "0") int page,
@RequestParam(required = false) String category,
Model model) {
model.addAttribute("posts", postService.getPostPage(page, category));
model.addAttribute("categories", postService.getCategoryList());
return "notes/list";
}
@GetMapping("/{id}")
public String detail(@PathVariable Long id, Model model) {
model.addAttribute("post", postService.getPost(id));
return "notes/detail";
}
}
REST API 컨트롤러
@RestController
@RequestMapping("/api/v1/posts")
@RequiredArgsConstructor
@Slf4j
public class PostApiController {
private final PostService postService;
@PostMapping
public ResponseEntity<ApiResponse<PostDetailRes>> regPost(
@RequestBody @Valid PostCreateReq req) {
log.info("노트 등록 요청: title={}", req.getTitle());
return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiResponse.ok(postService.regPost(req)));
}
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<PostDetailRes>> getPost(@PathVariable Long id) {
return ResponseEntity.ok(ApiResponse.ok(postService.getPost(id)));
}
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<PostDetailRes>> uptPost(
@PathVariable Long id,
@RequestBody @Valid PostUpdateReq req) {
return ResponseEntity.ok(ApiResponse.ok(postService.uptPost(id, req)));
}
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Void>> delPost(@PathVariable Long id) {
postService.delPost(id);
return ResponseEntity.ok(ApiResponse.ok(null));
}
}
REST URL 규칙
GET /api/v1/posts # 목록 조회
GET /api/v1/posts/{id} # 단건 조회
POST /api/v1/posts # 등록
PUT /api/v1/posts/{id} # 전체 수정
PATCH /api/v1/posts/{id} # 부분 수정
DELETE /api/v1/posts/{id} # 삭제
- URL: 소문자 케밥케이스 (
/api/v1/news-items) - 복수형 명사 사용 (
/posts,/news-items) - 버전 포함 (
/api/v1/)
HTTP 상태코드 기준
| 상황 | 코드 |
|---|---|
| 조회 성공 | 200 OK |
| 생성 성공 | 201 Created |
| 잘못된 요청 | 400 Bad Request |
| 인증 실패 | 401 Unauthorized |
| 권한 없음 | 403 Forbidden |
| 리소스 없음 | 404 Not Found |
| 서버 오류 | 500 Internal Server Error |
응답 형식 (ApiResponse)
모든 REST API 응답은 com.scraping.agent.global.common.ApiResponse<T> 래퍼를 사용한다.
// 성공
return ResponseEntity.ok(ApiResponse.ok(data));
// 생성 성공
return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.ok(data));
// 실패 (GlobalExceptionHandler에서 자동 처리)
return ResponseEntity.badRequest().body(ApiResponse.fail("에러 메시지"));
응답 JSON 형식:
{
"success": true,
"data": { ... },
"error": null
}
{
"success": false,
"data": null,
"error": "에러 메시지"
}
입력값 검증
// VO에 Bean Validation 어노테이션
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class PostCreateReq {
@NotBlank(message = "제목은 필수입니다.")
@Size(max = 200, message = "제목은 200자 이내여야 합니다.")
private String title;
@NotBlank(message = "내용은 필수입니다.")
private String content;
@NotBlank(message = "카테고리는 필수입니다.")
private String category;
}
// 컨트롤러에서 @Valid 적용
@PostMapping
public ResponseEntity<ApiResponse<PostDetailRes>> regPost(
@RequestBody @Valid PostCreateReq req) { ... }
검증 실패 시 GlobalExceptionHandler에서 400 응답으로 자동 처리된다.
컨트롤러 체크리스트
- [ ]
@RequiredArgsConstructor생성자 주입 사용 - [ ]
@Slf4j로그 선언 - [ ] 비즈니스 로직 없음 (Service 위임)
- [ ] Entity 직접 반환 금지 (VO 반환)
- [ ] Request VO에
@Valid적용 - [ ] REST API는
ApiResponse<T>래퍼 사용 - [ ] URL 소문자 케밥케이스