댓글 REST API의 개요

14장에서 댓글 엔티티와 레파지토리를 만들었다면 15장에서는 댓글 REST 컨트롤러, 서비스, DTO를 만들어 볼 것이다.

댓글 REST 컨트롤러에서는 클라이언트 요청에 응답하며 뷰가 아닌 데이터를 반환할 것이고,

서비스에서는 처리 흐름을 담당하며 예외 상황 발생 시 트랜잭션으로 롤백시킬 것이고,

DTO에서는 사용자에게 보여 줄 댓글 정보를 담은 것으로 단순히 클라이언트와 서버 간에 댓글 JSON 데이터를 전송할 것이다.

댓글 CRUD를 하려면 REST API 주소가 필요한데 아래 그림과 같이 여기에선 설계할 것이다.


댓글 컨트롤러와 서비스 틀 만들기

api 패키기에 CommentApiController라는 이름으로 REST 컨트롤러를 생성해 주는데,

서비스와 협업할 수 있도록 댓글 서비스 객체도 주입해 준다.

package com.example.firstproject.api;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CommentApiController {

    @Autowired
    private CommentService commentService; // 댓글 서비스 객체 주입



}

아직 서비스 파일을 생성하지 않아 CommentService에 빨간색 에러 표시가 날 것인데,

에러를 없애기 위해 service 패키지에 서비스 파일을 생성해 주고 초기 설정을 해준다.

package com.example.firstproject.service;

import com.example.firstproject.repository.ArticleRepository;
import com.example.firstproject.repository.CommentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class CommentService {
    
    @Autowired
    private CommentRepository commentRepository;    // 댓글 레퍼지토리 객체 주입

    @Autowired
    private ArticleRepository articleRepository;    // 게시글 레퍼지토리 객체 주입
}

다시 컨트롤러 파일로 돌아와 컨트롤러가 처리할 기능을 주석으로 작성해 두고 하나씩 구현해 볼 것이다.


댓글 조회하기

컨트롤러에 댓글 조회 메서드를 우선 아래와 같이 만들어준다.

@GetMapping("/api/articles/{articleId}/comments")   // 댓글 조회 요청 접수
public  ResponseEntity<List<CommentDto>> comments(@PathVariable Long articleId) {
    return null;
        
}

DTO를 아직 생성해 주지 않아 빨간색으로 표시될 것인데,

댓글 서비스에 조회 작업을 위임해 얻은 결과를 클라이언트에 응답하는 과정의 코드를 추가하고 DTO도 생성해 준다.

    // 1. 댓글 조회
    @GetMapping("/api/articles/{articleId}/comments")   // 댓글 조회 요청 접수
    public  ResponseEntity<List<CommentDto>> comments(@PathVariable Long articleId) {
        // 서비스에 위임
        List<CommentDto> dtos = commentService.comments(articleId);
        // 결과 응답
        return ResponseEntity.status(HttpStatus.OK).body(dtos);

    }
package com.example.firstproject.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@AllArgsConstructor // 모든 필드를 매개변수로 갖는 생상자 자동 생성
@NoArgsConstructor  // 매개변수가 아예 없는 기본 생성자 자동 생성
@Getter // 각 필드 값을 조회할 수 있는 getter 메서드 자동 생성
@ToString   // 모든 필드를 출력할 수 있는 toString 메서드 자동 생성
public class CommentDto {
    
    private Long id;    // 댓글의 id
    private Long articleId;    // 댓글의 부모 id
    private String nickname;    // 댓글 작성자
    private String body;    // 댓글 본문

}

이제 요청을 처리할 서비스를 생성해 주면 컨트롤러에 에러 없이 코드 작성을 완료할 수 있다.

서비스가 DB에서 데이터를 가져올 때 레파지토리에 시킨다고 했으므로 14장에서 만든 findByArticleId(articleId) 메서드를 호출한다.

이렇게 하면 articleId번 게시글의 모든 댓글을 가져오고 이것을 comments 변수에 저장한다.

그다음은 조회한 댓글 엔티티 목록을 DTO 목록으로 변환하기 위해 CommentDto를 저장하는 빈 ArrayList를 만들고,

List<CommentDto> 타입의 변수에 저장한다.

    public List<CommentDto> comments(Long articleId) {
        // 1. 댓글 조회
        List<Comment> comments = commentRepository.findByArticleId(articleId);

        // 2. 엔티티 -> DTO 변환
        List<CommentDto> dtos = new ArrayList<CommentDto>();
        for (int i = 0; i < comments.size(); i++) {     // 조회한 댓글 엔티티 수 만큼 반복
            Comment c = comments.get(i);        // 조회한 댓글 엔티티 하나씩 가져오기
            CommentDto dto = CommentDto.createCommentDto(c);        // 엔티티를 DTO로 변환
            dtos.add(dto);     // 변환한 DTO를 dtos 리스트에 삽입
        }

        // 3. 결과 반환
        return dtos;
    }

빨간색으로 표시된 createCommentDto 메서드를 자동완성기능의 도움을 받아 만들어 주도록 한다.

앞에 public static 즉 정적 메서드로 선언돼 있을 것인데 이는 객체 생성 없이 호출 가능한 메서드라는 뜻이라고 한다.

    public static CommentDto createCommentDto(Comment comment) {
        return new CommentDto(
                comment.getId(), 
                comment.getArticle().getId(),
                comment.getNickname(),
                comment.getBody()
        );
    }

댓글 조회 요청이 실제로 잘 처리되는지 확인하기 위해 FirstprojectApplication에서 실행시킨 후,

Talend API Tester로 댓글이 있는 4번 게시글을 조회하면 아래와 같이 잘 나오는 것을 볼 수 있다.

여기서 한발 더 나아가 서비스 코드를 개선해 보자면 서비스 코드의 for문을 스트림 문법으로 개선해 보는 작업을 해 준다.

    public List<CommentDto> comments(Long articleId) {

        /*
        // 1. 댓글 조회
        List<Comment> comments = commentRepository.findByArticleId(articleId);

        // 2. 엔티티 -> DTO 변환
        List<CommentDto> dtos = new ArrayList<CommentDto>();
        for (int i = 0; i < comments.size(); i++) {     // 조회한 댓글 엔티티 수 만큼 반복
            Comment c = comments.get(i);        // 조회한 댓글 엔티티 하나씩 가져오기
            CommentDto dto = CommentDto.createCommentDto(c);        // 엔티티를 DTO로 변환
            dtos.add(dto);  // 변환한 DTO를 dtos 리스트에 삽입
        }
         */

        // 3. 결과 반환
        return commentRepository.findByArticleId(articleId)
                .stream()
                .map(comment -> CommentDto.createCommentDto(comment))
                .collect(Collectors.toList());
    }

댓글 생성하기

댓글 생성을 위해서 조회할 경우와 유사하게 우선 메서드를 컨트롤러에 작성해 주는데 이번에는 PostMapping을 해주어야 한다.

    // 2. 댓글 생성
    @PostMapping("/api/articles/{articleId}/comments")
    public ResponseEntity<CommentDto> create(@PathVariable Long articleId,
                                             @RequestBody CommentDto dto) {
        // 서비스에 위임
        CommentDto createdDto = commentService.create(articleId, dto);
        // 결과 응답
        return ResponseEntity.status(HttpStatus.OK).body(createdDto);
    }

위와 같이 작성을 해 주면 create 메서드가 생성되지 않아 빨간색으로 표시될 것이니 서비스에 메서드 추가해 준다.

코드를 작성 시 주의 점은 create() 메서드는 DB 내용을 바꾸기 때문에 실패 경우를 대비하여 트랜잭션 처리 해주어야 한다.

    @Transactional
    public CommentDto create(Long articleId, CommentDto dto) {
        // 1. 게시글 조회 및 예외 발생
        Article article = articleRepository.findById(articleId)
                .orElseThrow(() -> new IllegalArgumentException("댓글 생성 실패! 대상 게시글이 없습니다."));

        // 2. 댓글 엔티티 생성
        Comment comment = Comment.createComment(dto, article);

        // 3. 댓글 엔티티를 DB에 저장
        Comment created = commentRepository.save(comment);

        // 4. DTO로 변환해 반환
        return CommentDto.createCommentDto(created);
    }

위와 같이 작성하였다면 이제 빨간색으로 표시된 createComment() 엔티티 생성 메서드를 아래처럼 만들어준다.

    public static Comment createComment(CommentDto dto, Article article) {
        // 예외 발생
        if(dto.getId() != null) {
            throw new IllegalArgumentException("댓글 생성 실패! 댓글의 id가 없어야 합니다.");
        }
        
        if(dto.getArticleId() != null) {
            throw new IllegalArgumentException("댓글 생성 실패! 게시글의ㅣ id가 잘못됐습니다.");
        }
        
        // 엔티티 생성 및 반환
        return new Comment(
                dto.getId(),
                article,
                dto.getNickname(),
                dto.getBody()
        );
    }

 

이제 정상적으로 처리되는지 테스트를 위해 Talend API Tester로 요청을 보내면 아래와 같이 나오는 것을 볼 수 있다.

예외 상황을 테스트하기 위해 JSON 데이터의 articleId를 5로 수정 후 send 하면 아래와 같이 에러를 볼 수 있다.

댓글 수정하기와 삭제하기도 유사한 과정으로 진행하면 되니 실행 결과만 첨부하도록 하겠다.