Skip to content

Commit

Permalink
✨ feat: Weather Service 응답 정규화
Browse files Browse the repository at this point in the history
  • Loading branch information
seheonnn authored May 9, 2024
2 parents 1585522 + 5459133 commit ad3723e
Show file tree
Hide file tree
Showing 17 changed files with 259 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.waither.weatherservice.controller;

public class WeatherController {
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
import org.springframework.web.bind.annotation.RestController;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.waither.weatherservice.dto.AccuweatherTestRequest;
import com.waither.weatherservice.dto.AirTestRequest;
import com.waither.weatherservice.dto.ForeCastTestRequest;
import com.waither.weatherservice.dto.MsgTestRequest;
import com.waither.weatherservice.dto.request.AccuweatherTestRequest;
import com.waither.weatherservice.dto.request.AirTestRequest;
import com.waither.weatherservice.dto.request.ForeCastTestRequest;
import com.waither.weatherservice.dto.request.MsgTestRequest;
import com.waither.weatherservice.service.WeatherService;

import lombok.RequiredArgsConstructor;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.waither.weatherservice.dto;
package com.waither.weatherservice.dto.request;

public record AccuweatherTestRequest(
double latitude,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.waither.weatherservice.dto;
package com.waither.weatherservice.dto.request;

public record AirTestRequest(
String searchDate
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.waither.weatherservice.dto;
package com.waither.weatherservice.dto.request;

public record ForeCastTestRequest(
int nx,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.waither.weatherservice.dto;
package com.waither.weatherservice.dto.request;

public record MsgTestRequest(
String location
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.waither.weatherservice.dto.request;

import lombok.Builder;

@Builder
public record WeatherRequest() {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.waither.weatherservice.dto.response;

import lombok.Builder;

@Builder
public record WeatherResponse() {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.waither.weatherservice.exception;

import com.waither.weatherservice.response.status.BaseErrorCode;

import lombok.Getter;

@Getter
public class CustomException extends RuntimeException {

private final BaseErrorCode errorCode;

public CustomException(BaseErrorCode errorCode) {
this.errorCode = errorCode;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.waither.weatherservice.exception;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import com.waither.weatherservice.response.ApiResponse;
import com.waither.weatherservice.response.ErrorCode;
import com.waither.weatherservice.response.status.BaseErrorCode;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

// @Valid 유효성 검사를 실패했을 시
@ExceptionHandler(MethodArgumentNotValidException.class)
protected ResponseEntity<ApiResponse<Map<String, String>>> handleMethodArgumentNotValidException(
MethodArgumentNotValidException ex
) {
// 실패한 validation 을 담을 Map
Map<String, String> failedValidations = new HashMap<>();
List<FieldError> fieldErrors = ex.getBindingResult().getFieldErrors();
// fieldErrors 를 순회하며 failedValidations 에 담는다.
fieldErrors.forEach(error -> failedValidations.put(error.getField(), error.getDefaultMessage()));
ApiResponse<Map<String, String>> errorResponse = ApiResponse.onFailure(
ErrorCode.VALIDATION_FAILED.getCode(),
ErrorCode.VALIDATION_FAILED.getMessage(),
failedValidations);
return ResponseEntity.status(ex.getStatusCode()).body(errorResponse);
}

// Custom 예외에 대한 처리
@ExceptionHandler({CustomException.class})
public ResponseEntity<ApiResponse<Void>> handleCustomException(CustomException e) {
log.warn("[WARNING] Custom Exception : {}", e.getErrorCode());
BaseErrorCode errorCode = e.getErrorCode();
return ResponseEntity.
status(errorCode.getHttpStatus())
.body(errorCode.getErrorResponse());
}

@ExceptionHandler({Exception.class})
public ResponseEntity<ApiResponse<String>> handleAllException(Exception e) {
log.error("[WARNING] Internal Server Error : {} ", e.getMessage());
BaseErrorCode errorCode = ErrorCode.INTERNAL_SERVER_ERROR_500;
ApiResponse<String> errorResponse = ApiResponse.onFailure(
errorCode.getCode(),
errorCode.getMessage(),
e.getMessage()
);
return ResponseEntity
.status(errorCode.getHttpStatus())
.body(errorResponse);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.waither.weatherservice.exception;

import com.waither.weatherservice.response.status.BaseErrorCode;

public class WeatherExceptionHandler extends CustomException {
public WeatherExceptionHandler(BaseErrorCode errorCode) {
super(errorCode);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.waither.weatherservice.exception.WeatherExceptionHandler;
import com.waither.weatherservice.response.WeatherErrorCode;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -69,14 +71,14 @@ public List<ForeCastOpenApiResponse.Item> callForeCastApi(
.accept(MediaType.APPLICATION_JSON)
.retrieve().bodyToMono(ForeCastOpenApiResponse.class)
.onErrorResume(throwable -> {
throw new OpenApiException(RESPONSE_EXCEPTION_MSG);
throw new WeatherExceptionHandler(WeatherErrorCode.WEATHER_OPENAPI_ERROR);
})
.block().getResponse();

if (response.getHeader().getResultCode().equals("00")) {
return response.getBody().getItems().getItem();
} else {
throw new OpenApiException(response.getHeader().getResultMsg());
throw new WeatherExceptionHandler(WeatherErrorCode.WEATHER_OPENAPI_ERROR);
}
}

Expand Down Expand Up @@ -124,7 +126,7 @@ public List<MsgOpenApiResponse.RowData> callDisasterMsgApi(String location) thro
})
.retrieve().bodyToMono(String.class)
.onErrorResume(throwable -> {
throw new OpenApiException(RESPONSE_EXCEPTION_MSG);
throw new WeatherExceptionHandler(WeatherErrorCode.WEATHER_OPENAPI_ERROR);
})
.block();

Expand All @@ -135,7 +137,8 @@ public List<MsgOpenApiResponse.RowData> callDisasterMsgApi(String location) thro
return response.getDisasterMsg().get(1).getRow();
} else {
String resultMsg = response.getDisasterMsg().get(0).getHead().get(2).getResultData().getResultMsg();
throw new OpenApiException(resultMsg);
log.info("[*] OpenApi Error : {}", resultMsg);
throw new WeatherExceptionHandler(WeatherErrorCode.WEATHER_OPENAPI_ERROR);
}
}

Expand All @@ -161,8 +164,11 @@ public List<AirKoreaOpenApiResponse.Items> callAirKorea(String searchDate) throw
AirKoreaOpenApiResponse.Response response = webClient.get()
.uri(uri)
.accept(MediaType.APPLICATION_JSON)
.retrieve().bodyToMono(AirKoreaOpenApiResponse.class)
.blockOptional().orElseThrow(() -> new OpenApiException(RESPONSE_EXCEPTION_MSG)).getResponse();
.retrieve()
.bodyToMono(AirKoreaOpenApiResponse.class)
.blockOptional()
.orElseThrow(() -> new WeatherExceptionHandler(WeatherErrorCode.WEATHER_OPENAPI_ERROR))
.getResponse();

if (response.getHeader().getResultCode().equals("00")) {

Expand All @@ -178,7 +184,8 @@ public List<AirKoreaOpenApiResponse.Items> callAirKorea(String searchDate) throw

return items;
} else {
throw new OpenApiException(response.getHeader().getResultMsg());
log.info("[*] OpenApi Error : {}", response.getHeader().getResultMsg());
throw new WeatherExceptionHandler(WeatherErrorCode.WEATHER_OPENAPI_ERROR);
}
}

Expand Down Expand Up @@ -215,7 +222,7 @@ public String callAccuweatherLocationApi(double latitude, double longitude) thro
.accept(MediaType.APPLICATION_JSON)
.retrieve().bodyToMono(String.class)
.onErrorResume(throwable -> {
throw new OpenApiException(RESPONSE_EXCEPTION_MSG);
throw new WeatherExceptionHandler(WeatherErrorCode.WEATHER_OPENAPI_ERROR);
})
.block();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.waither.weatherservice.response;

import org.springframework.http.HttpStatus;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@JsonPropertyOrder({"code", "message", "result"})
public class ApiResponse<T> {

// HTTP 상태 코드나 사용자 정의 코드
private final String code;
// API 요청에 대한 설명이나 상태 메시지
private final String message;
// result 값은 null이 아닐 때만 응답에 포함시킨다.
@JsonInclude(JsonInclude.Include.NON_NULL)
// 결과 데이터. (보통 dto -> json 파싱 될 예정)
private T result;

// 성공한 경우 응답 생성
public static <T> ApiResponse<T> onSuccess(T result) {
return new ApiResponse<>(String.valueOf(HttpStatus.OK.value()), HttpStatus.OK.getReasonPhrase(), result);
}

// 성공한 경우 응답 생성 (상태 코드 지정 가능)
public static <T> ApiResponse<T> onSuccess(HttpStatus status, T result) {
return new ApiResponse<>(String.valueOf(status.value()), status.getReasonPhrase(), result);
}

// 실패한 경우 응답 생성
public static <T> ApiResponse<T> onFailure(String code, String message, T result) {
return new ApiResponse<>(code, message, result);
}

// 실패한 경우 응답 생성 (데이터 없음)
public static <T> ApiResponse<T> onFailure(String statusCode, String message) {
return new ApiResponse<>(statusCode, message, null);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.waither.weatherservice.response;

import org.springframework.http.HttpStatus;

import com.waither.weatherservice.response.status.BaseErrorCode;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum ErrorCode implements BaseErrorCode {

// 일반적인 ERROR 응답
BAD_REQUEST_400(HttpStatus.BAD_REQUEST,
"COMMON400",
HttpStatus.BAD_REQUEST.getReasonPhrase()),
UNAUTHORIZED_401(HttpStatus.UNAUTHORIZED,
"COMMON401",
HttpStatus.UNAUTHORIZED.getReasonPhrase()),
FORBIDDEN_403(HttpStatus.FORBIDDEN,
"COMMON403",
HttpStatus.FORBIDDEN.getReasonPhrase()),
NOT_FOUND_404(HttpStatus.NOT_FOUND,
"COMMON404",
HttpStatus.NOT_FOUND.getReasonPhrase()),
INTERNAL_SERVER_ERROR_500(
HttpStatus.INTERNAL_SERVER_ERROR,
"COMMON500",
HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()),

// 유효성 검사
VALIDATION_FAILED(HttpStatus.BAD_REQUEST, "VALID400_0", "입력값에 대한 검증에 실패했습니다.");

private final HttpStatus httpStatus;
private final String code;
private final String message;

@Override
public ApiResponse<Void> getErrorResponse() {
return ApiResponse.onFailure(code, message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.waither.weatherservice.response;

import org.springframework.http.HttpStatus;

import com.waither.weatherservice.response.status.BaseErrorCode;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum WeatherErrorCode implements BaseErrorCode {

WEATHER_EXAMPLE_ERROR(HttpStatus.BAD_REQUEST, "WEAT4000", "날씨 에러입니다."),
WEATHER_OPENAPI_ERROR(HttpStatus.BAD_REQUEST, "WEAT4001", "OpenApi 관련 오류입니다.");

private final HttpStatus httpStatus;
private final String code;
private final String message;

@Override
public ApiResponse<Void> getErrorResponse() {
return ApiResponse.onFailure(code, message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.waither.weatherservice.response.status;

import org.springframework.http.HttpStatus;

import com.waither.weatherservice.response.ApiResponse;

public interface BaseErrorCode {

HttpStatus getHttpStatus();

String getCode();

String getMessage();

ApiResponse<Void> getErrorResponse();
}

0 comments on commit ad3723e

Please sign in to comment.