[사이드프로젝트] 그저 그런 REST API로 괜찮은가? - 진정한 REST API 구현해보기 - GET Event 구현하기

Get Event Test 작성


이번에는 단일 Event를 가져오는 로직을 구현할 것이다.

여기서 고려해야할 점은

  • 단일로 불러왔을 때, HATEAOS를 만족하는가.
  • 존재하지 않는 Event를 가져올 때, 에러 메시지를 보내는가.

테스트 코드를 작성하면 다음과 같다.

    @Test
    public void get_event_success() throws Exception {
        Event event = createEvent(0);
        this.eventRepository.save(event);

        mockMvc.perform(get("/api/events/{id}", event.getId()))
                .andDo(print())
                .andExpect(jsonPath("event").exists())
                .andExpect(jsonPath("_links").exists());
    }

    @Test
    public void get_event_not_found_event() throws Exception {
        //Given
        Integer wrongId = 10010;

        mockMvc.perform(get("/api/events/{id}", wrongId))
                .andDo(print())
                .andExpect(status().isNotFound())
                .andExpect(jsonPath("timestamp").exists())
                .andExpect(jsonPath("status").exists())
                .andExpect(jsonPath("error").exists())
                .andExpect(jsonPath("message").exists());
    }

Service 로직 구현


Service layer에서는 JPA를 통해서 데이터를 불러온 후, null 체크를 통해 Event가 존재하는 지 안하는 지 예외처리를 할 것이다.

JPA에서 findBy로 데이터를 가져올 시, Optional<> 객체에 담겨서 온다.
이 Optional 객체의 isEmpty()가 참일 경우, 데이터가 존재하지 않기 때문에 if문과 throw문을 통해서 예외처리를 한다.

    public Event read(Integer id){
        Optional<Event> findEvent =  this.eventRepository.findById(id);
        if(findEvent.isEmpty())
            throw new CustomException(HttpStatus.NOT_FOUND, "Event not found");
        return findEvent.get();
    }

CustomException 구현 및 ErrorResponse 수정


이제 예외 처리를 할 때, Custom Error를 사용하여 내가 필요로 하는 에러를 던지려고 한다.

  • CustomException.java
package com.carrykim.restapi.event.global.exception;

import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
public class CustomException extends RuntimeException{

    private HttpStatus httpStatus;
    private String message;

    public CustomException(HttpStatus httpStatus, String message){
        this.httpStatus = httpStatus;
        this.message = message;
    }
}
  • ErrorResponse.java

package com.carrykim.restapi.event.global.exception;

import lombok.Builder;
import lombok.Getter;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Getter
public class ErrorResponse {

    private final LocalDateTime timestamp = LocalDateTime.now();
    private int status;
    private List<String> message;
    private String error;

    public ErrorResponse(HttpStatus httpStatus , BindingResult bindingResult) {
        this.status = httpStatus.value();
        this.error = httpStatus.name();
        this.message = bindingResult.getFieldErrors().stream()
                .map(e -> {
                    String m = e.getField() + " : " + e.getDefaultMessage();
                    return m;
                })
                .collect(Collectors.toList());
    }

    public ErrorResponse(CustomException customException){
        this.status = customException.getHttpStatus().value();
        this.error = customException.getHttpStatus().name();

        this.message = new ArrayList<>();
        this.message.add(customException.getMessage());
    }

}

EventExceptionHandler 수정


이제 EventExceptionHandler를 수정해서 CustomException이 던져지면 이를 받아서 에러 메시지를 보낸다.

package com.carrykim.restapi.event.global.exception;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.hateoas.MediaTypes;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class EventExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity validationErrorException(final MethodArgumentNotValidException e) {
        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body(new ErrorResponse(HttpStatus.BAD_REQUEST,e.getBindingResult()));
    }

    @ExceptionHandler(CustomException.class)
    public ResponseEntity customErrorException(final CustomException e){
        return ResponseEntity
                .status(e.getHttpStatus().value())
                .body(new ErrorResponse(e));
    }

}

EventController 수정


마지막으로 Service layer에서 넘긴 Event 객체를 EventResource 객체에 담아 URI 매핑 후 반환한다.

	@GetMapping("/{id}")
    public ResponseEntity read(@PathVariable Integer id){
        Event event = this.eventService.read(id);
        EventResource eventResource = new EventResource(event);
        addLinks(eventResource);
        return ResponseEntity.ok(eventResource);
    }

테스트 결과


좋은 웹페이지 즐겨찾기