[Spring/기초] api 공통 응답 포맷 + 예외 처리 합치기 (2)Spring/Spring 기초2024. 7. 15. 17:48
Table of Contents
728x90
반응형
공통 응답 포맷을 만드는 이유
- 일관된 응답 형식 유지
모든 API 응답을 일관된 형식으로 유지할 수 있다.
이는 전체적인 프로그램의 유지 보수성을 높인다. - 로깅 및 모니터링
같은 방식으로 응답을 하니 로깅 및 모니터링이 더 쉬워진다. - 보안 및 데이터 검증
응답을 전역적으로 처리함으로써 민감한 정보를 숨기거나 데이터를 검증하는 등의 보안 작업을 일관되게 수행할 수 있다.
ResponseBodyAdvice 공통 응답 포맷
1. 공통 응답 코드 형식 정하기
public interface ResponseCode {
String name();
HttpStatus getHttpStatus();
String getData();
}
클라이언트에게 공통 응답 코드를 어떤 형식으로 보내줄 것인지 정의한다.
@Getter
@RequiredArgsConstructor
public enum CommonResponseCode implements ResponseCode {
SUCCESS(HttpStatus.OK, "요청이 성공됐습니다");
private final HttpStatus httpStatus;
private final String data;
}
그후 공통응답에 사용할 코드를 상속을 받아서 정의한다.
2. 공통 응답 클래스 생성하기
클라이언트에 건내줄 응답을 만들어야 한다. code가 만일 지정되지 않았다면 SUCCESS로 통일한다.
@Getter
@Builder
@RequiredArgsConstructor
public class SuccessResponse<T> {
private final String status;
private final String msg;
private final T data;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private final String code = "SUCCESS";
}
3. 공통 응답 헨들러 Utill 생성하기
공통응답을 어떻게 작성할껀지 정한다. 나는 Body 안에 아래와 같은 형식으로 값을 넣어줄 것이다.
{
status :
msg :
data : {
}
}
public class ResponseHandlerUtil {
public static <T> ResponseEntity<Object> handleSuccess(final ResponseCode responseCode, T data) {
return ResponseEntity.status(responseCode.getHttpStatus())
.body(makeSuccessResponse(responseCode, data));
}
private static <T> SuccessResponse<T> makeSuccessResponse(final ResponseCode responseCode, T data) {
return SuccessResponse.<T>builder()
.status(responseCode.getHttpStatus().toString())
.msg(responseCode.getData())
.data(data)
.build();
}
}
4. 공통 응답 클래스 헨들러 생성하기
지금까지 사용한 모든 것들을 이용해서 핸들러를 생성하면 된다.
@RestControllerAdvice(basePackages = "darkoverload.itzip")
@Slf4j
public class GlobalSuccessHandler implements ResponseBodyAdvice<Object> {
private static final Logger logger = LoggerFactory.getLogger(GlobalSuccessHandler.class);
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
logger.info("body: {}", body);
if (!(body instanceof SuccessResponse) && !(body instanceof String) && !(body instanceof ProblemDetail) && !(body instanceof ExceptionResponse)) {
Object handledBody = ResponseHandlerUtil.handleSuccess(CommonResponseCode.SUCCESS, body).getBody();
logger.info("body responseBody: {}", handledBody);
return handledBody;
}
if (body instanceof String) {
String handledBody = "{\"status\":\"200 OK\",\"msg\":\"요청이 성공됐습니다\",\"data\":\"" + body + "\"}";
logger.info("string responseBody: {}", handledBody);
return handledBody;
}
return body;
}
}
basePakages를 사용한 이유는 swagger를 사용할때 응답이 이를 통해서 적어져야 하기 때문이다.
ResponseBodyAdvice를 상속받아서 그에 필요한 것들을 적어준다.
supports 를 통해서 반환한 값을 beforeBodyWrite가 받고
여기서 에러인지 아닌지 확인하고 String이면 다른 방식으로 출력하도록 한다.
ResponseBodyAdvice 공통 응답 포맷
1. TestController 추가
@GetMapping("/success")
public String successs() {
return "성공했습니다";
}
@GetMapping("/success2")
public ResponseEntity<Object> successs2() {
Map<String, String> response = new HashMap<>();
response.put("field1", "field1Value");
response.put("field2", "field2Value");
return ResponseEntity.ok(response);
}
@GetMapping("/success3")
public Map<String, String> successs3() {
Map<String, String> response = new HashMap<>();
response.put("field1", "field1Value");
response.put("field2", "field2Value");
return response;
}
String리턴, Response리턴, 값으로 return을 모두 테스트 해보기 위해서 end포인트 작성
2. TestCode 생성
@Test
void getSuccess2Response() {
Assertions.assertTimeout(Duration.ofSeconds(2), () -> {
mockMvc.perform(get("/test/success2")
.contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultHandlers.log())
.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value("200 OK"))
.andExpect(jsonPath("$.msg").value("요청이 성공됐습니다"))
.andExpect(jsonPath("$.data.field1").value("field1Value"))
.andExpect(jsonPath("$.data.field2").value("field2Value"));
});
}
@Test
void getSuccessResponse() throws Exception {
mockMvc.perform(get("/test/success")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value("200 OK"))
.andExpect(jsonPath("$.msg").value("요청이 성공됐습니다"))
.andExpect(jsonPath("$.data").value("성공했습니다"));
}
@Test
void getSuccess3Response() {
Assertions.assertTimeout(Duration.ofSeconds(2), () -> {
mockMvc.perform(get("/test/success3")
.contentType(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultHandlers.log())
.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value("200 OK"))
.andExpect(jsonPath("$.msg").value("요청이 성공됐습니다"))
.andExpect(jsonPath("$.data.field1").value("field1Value"))
.andExpect(jsonPath("$.data.field2").value("field2Value"));
});
}
테스트 코드는 exception이랑 같은 방식으로 만들어야한다.
728x90
반응형
'Spring > Spring 기초' 카테고리의 다른 글
[Spring/기초] Valid 예외 처리 + 404, 405 (2) (0) | 2024.09.13 |
---|---|
[Spring/기초] 전역 예외 처리 + Test Code (1) (0) | 2024.07.14 |
[Spring/기초] RestController 완벽 정리 (return type, 파라미터, 추가 개념 및 기능) (0) | 2024.06.23 |
[Spring/기초] 환경 변수 파일 사용하기 (env.properties) (0) | 2024.05.28 |
[Spring/기초] Service (0) | 2024.02.07 |
@코딩하는 자연대생 :: 자연대생도 코딩을 하고 싶어
Coding, Software, Computer Science 내가 공부한 것들 잘 이해했는지, 설명할 수 있는지 적는 공간