[UMC server(Spring boot) 8주차] API 응답 통일 & 에러 핸들러

Sujung Shin·2023년 11월 29일
0



📑 API 응답 통일


지난주차까지 데이터에 대해 핸들링했다면,
이번 주차는 본격적으로 프로젝트에 관한 설정을 해봅시다.

우선적으로 API 응답의 통일이 필요하겠죠.
또한 에러 핸들링(Server, Client가 잘못할 경우 모두 포하)과 스웨거 세팅이 필요합니다.

API 응답 통일은 왜 필요할까요?

JSON 형태의 API 응답의 형식은 보통 다음과 같습니다.

{	
	isSuccess : Boolean
    code : String
    message : String
    result : {응답으로 필요한 또 다른 json}
}
  • code : HTTP 상태 코드 + 세부적인 결과를 알려주기 위해 사용
  • message : code에 추가적으로 어떤 결과인지 알려주기 위해 사용
  • 실패한 경우에는 result가 딱히 필요 없어서 NULL값을 준다.




응답의 경우 Code라는 형태의 enum으로 형태를 관리합니다.
성공 응답과 실패 응답을 하나의 enum으로 만들거나, 분리하여 관리할 수도 있습니다.

예시를 들어 다음과 같은 실습을 진행해봅시다:

Step 1. apiPayload 라는 이름을 지닌 패키지를 생성합니다.
해당 패키지 아래에 ApiResponse 클래스, code 파일을 만들어주세요.

그 다음 통일된 API 응답을 위한 ApiResponse를 만들어봅시다.

package umc.study.apiPayload;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
@JsonPropertyOrder({"isSuccess", "code", "message", "result"})
public class ApiResponse<T> {

		@JsonProperty("isSuccess")
    private final Boolean isSuccess;	// 성공인지 아닌지 알려주는 필드
    private final String code;			// HTTP 상태코드+세부적인 응답 상황
    private final String message;		// code에 추가적으로 우리에게 익숙한 문자로 상황을 알려줌
    @JsonInclude(JsonInclude.Include.NON_NULL)
    private T result;					// 실제로 클라이언트에게 필요한 데이터


    // 성공한 경우 응답 생성(onSuccess)
	// 응답 내부에 들어갈 code를 아직 생성하지 않아 주석처리하였습니다
//    public static <T> ApiResponse<T> onSuccess(T result){
//        return new ApiResponse<>(true, SuccessStatus._OK.getCode() , SuccessStatus._OK.getMessage(), result);
//    }
//
//    public static <T> ApiResponse<T> of(BaseCode code, T result){
	//        return new ApiResponse<>(true, code.getReasonHttpStatus().getCode() , code.getReasonHttpStatus().getMessage(), result);
//    }


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

result는 어떤 형태의 값이 올지 모르기에, Generic 으로 만들어줍시다.

api 응답 형태에 대해 알아봅니다.

{
  "isSuccess" : true,
  "code" : "2000"
  "message" : "OK"
  "result" :
   {
  		"testString" : "This is test!"
   }
}
  • isSuccess: 성공인지 아닌지 알려주는 필드
  • code: HTTP 상태코드 + 세부적인 응답상황을 알려줌
  • message: code에 추가적으로 우리에게 익숙한 문자로 상황을 알려주는 필드
  • result: 실제로 클라이언트에게 필요한 데이터가 담김

API 응답에 HTTP 상태코드로 대표되는 상황뿐만 아니라, 특정한 상황에서의 세부적인 상황설명을 더 해주는 것이 'code'와 'message'라고 보면 됩니다.

이 'code'와 'message'의 형식을 이제 만들어봅시다.

status들은 enum입니다.

  • enum(열거형) : ErrorStatus SuccessStatus
  • interface(인터페이스) : BaseCode BaseErrorCode
    → 이를 구체화하기 위한 Status에서 이 둘의 메소드를 반드시 오버라이드할 것을 강제해줍니다.
  • class(클래스) : ErrorReasonDTO ReasonDTO

CommonErrorStatus의 골조 코드(스켈레톤 코드)는 다음과 같습니다:

@Getter
@AllArgsConstructor
public enum CommonErrorStatus implements BaseErrorCode {

    // 가장 일반적인 응답
    _INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "COMMON500", "서버 에러, 관리자에게 문의 바랍니다."),
    _BAD_REQUEST(HttpStatus.BAD_REQUEST,"COMMON400","잘못된 요청입니다."),
    _UNAUTHORIZED(HttpStatus.UNAUTHORIZED,"COMMON401","인증이 필요합니다."),
    _FORBIDDEN(HttpStatus.FORBIDDEN, "COMMON403", "금지된 요청입니다."),
    ;

    // 멤버 관련 응답

    // ~~~ 관련 응답 ....


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

	// interface의 어노테이션을 통하여 DTO를 만드는 것을 확인할 수 있다.
    @Override 
    public ErrorReasonDTO getReason() {
        return ErrorReasonDTO.builder()
                .message(message)
                .code(code)
                .isSuccess(false)
                .build();
    }

    @Override
    public ErrorReasonDTO getReasonHttpStatus() {
        return ErrorReasonDTO.builder()
                .message(message)
                .code(code)
                .isSuccess(false)
                .httpStatus(httpStatus)
                .build()
                ;
    }
}

임시 API 만들어보기

🎯 GET /temp/test

  • query String: X
  • request body: X
  • request Header: X
  • response
{
	"isSuccess ": true,
	"code" : "2000",
	"message" : "OK",
	"result" : 
		{
			"testString" : "This is test!"
		}
}

Step 1. DTO를 우선적으로 만들어줍니다.

request body에 담겨오는 값이 없기 때문에, TempResponse만 작성한 화면입니다.

Step 2. converter을 만들어줍니다.
업로드중..

converter 패키지에 TempConverter 클래스를 만듭니다.
업로드중..





의존성 주입(dependency injection, DI)


업로드중..
어떤 객체가 사용하는 의존 객체를 직접 만들지 않고, 주입받아 사용하는 것입니다.
예를 들어, 위의 사례에서 배터리 일체형 장난감은 배터리가 떨어지면 장난감을 새로 구입해야겠죠?
이때, 장난감은 배터리에 의존하고 있습니다.
일체형 장난감의 코드는 다음과 같습니다:

public class Toy {
	private Battery battery;
    
    public Toy() { // 생성자에서 배터리 객체 생성
    	battery = new Battery(); 
    }
}

해당 코드를 보면 배터리가 떨어지면, 배터리 교체가 불가능하므로 새로운 장난감을 생성해야합니다.
유연성이 굉장히 떨어진다는 것을 알 수 있겠습니다.

반면, 배터리 분리형의 장난감의 경우에는 어떨까요?

public class Toy {

	private Battery battery;

	public Toy(Battery battery) {
		this.battery = battery;
	}

	public void setBattery(Battery battery) {
		this.battery = battery;
	}
}

생성자, 혹은 setter을 통해 배터리를 주입할 수 있습니다.
배터리가 다 떨어지더라도, 장난감을 새로 마련할 필요 없이 배터리만 교체하면 되므로 유연성이 보장됩니다.

즉, 스프링은 DI를 통해 모듈 간의 결합도가 낮아지고 유연성이 높아질 수 있습니다.

profile
백문이불여일타

0개의 댓글

관련 채용 정보