이번 강의(22강)에서는 글 작성 API를 POST 요청으로 구현하고,
요청 본문을 JSON 형태로 전달하는 방식까지 다뤘다.
이전 강의에서 정리했던 RsData 응답 구조를 그대로 유지하면서,
요청 DTO를 record 형태로 컨트롤러 내부에 선언하는 방식도 새롭게 등장했다.
HTTP 메서드는 역할이 명확하다.
글 작성은 새로운 리소스를 생성하는 행위이므로
조회(GET)가 아닌 POST 요청이 적절하다.
POST /api/v1/posts
글 작성 시 제목과 내용은 요청 본문(body)에 담아 전달한다.
{
"title": "제목",
"content": "내용"
}

@RestController
@RequestMapping("/api/v1/posts")
@RequiredArgsConstructor
public class ApiV1PostController {
private final PostService postService;
@GetMapping
@Transactional(readOnly = true)
public List<PostDto> getItems() {
return postService.findAll()
.stream()
.map(PostDto::new)
.toList();
}
@GetMapping("/{id}")
@Transactional(readOnly = true)
public PostDto getItem(@PathVariable int id) {
Post post = postService.findById(id).get();
return new PostDto(post);
}
@DeleteMapping("/{id}")
@Transactional
public RsData<Void> delete(@PathVariable int id) {
Post post = postService.findById(id).get();
postService.delete(post);
return new RsData<>(
"200-1",
"%d번 글이 삭제되었습니다.".formatted(id)
);
}
record PostWriteForm(
@NotBlank
@Size(min = 2, max = 100)
String title,
@NotBlank
@Size(min = 2, max = 5000)
String content
) {}
@PostMapping
@Transactional
public RsData<PostDto> write(@Valid @RequestBody PostWriteForm form) {
Post post = postService.write(form.title(), form.content());
return new RsData<>(
"200-1",
"%d번 글이 생성되었습니다.".formatted(post.getId()),
new PostDto(post)
);
}
}
기존 강의에서는 DTO를 별도 패키지로 분리했는데,
이번에는 컨트롤러 내부에 record를 선언했다.
이유는 명확하다.
PostWriteForm은 글 작성 API 전용이다.
다른 컨트롤러나 서비스에서 재사용되지 않는다.
→ 굳이 패키지로 분리할 필요가 없다.
record PostWriteForm(String title, String content) {}
→ 요청 데이터 전달용 DTO로 딱 적당
컨트롤러 안에 선언함으로써
“이 record는 이 API 전용이다”라는 의미가 명확해진다.
public RsData<PostDto> write(
@Valid @RequestBody PostWriteForm form
)
@RequestBody@Valid@NotBlank
@Size(min = 2, max = 100)
String title
→ 조건을 만족하지 않으면 컨트롤러 로직은 실행되지 않고 예외 발생
글 작성은 조회가 아닌 액션이므로
성공 여부와 메시지가 중요하다.
{
"resultCode": "200-1",
"msg": "4번 글이 생성되었습니다.",
"data": {
"id": 4,
"createDate": "...",
"modifyDate": "...",
"title": "제목",
"content": "내용"
}
}

조회 API는 대부분 성공 자체가 목적이다.
@GetMapping("/{id}")
public PostDto getItem(...)
→ 매번 resultCode, msg를 감싸면 오버스펙
반면에,
같은 액션 API는
프론트에서 “무슨 일이 일어났는지”가 중요하므로 RsData가 적합하다.
@RequestBody로 JSON → 객체 변환record는 요청 DTO로 매우 적합이번 강의를 통해 느낀 점은
“모든 걸 무조건 통일하는 게 정답은 아니다”라는 것이다.
이런 판단 기준이 결국
읽기 좋은 코드, 유지보수하기 좋은 코드로 이어진다는 걸 체감했다.