[横書きアイテム]そんなREST APIで大丈夫ですか?本格的なREST API-実装GETイベントの実装

31184 ワード

Get Event Testの作成


今回は,単一イベントを導入する論理を実現する.
ここで考えなければならないのは
  • 単一マウント時にHATEAOSを満たすか.
  • 存在しないイベントを取得した場合、エラーメッセージが送信されるかどうか.
  • 次のようにテストコードを記述します.
        @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 Layerは、JPAでデータをロードし、nullでイベントに異常があるかどうかを確認します.
    JPAからfindByにデータをインポートする場合は、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の実装とエラーレスポンスの変更


    今、異常処理を行うときは、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));
        }
    
    }
    

    Event Controlの変更


    最後に、サービスレイヤーによって渡されたイベントオブジェクトを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);
        }

    テスト結果