Backend

<Spring> Success Custom Response ์ ์šฉํ•˜๊ธฐ

wjdtkdgns 2023. 5. 2. 16:41

๐Ÿ“Œ ๊ธฐ์กด ํ”„๋กœ์ ํŠธ

ํ”„๋กœ์ ํŠธ์—์„œ ์ปค์Šคํ…€ ๋ฆฌ์Šคํฐ์Šค๋ฅผ ์ด์šฉํ•˜์—ฌ API response๋ฅผ ๋ณด๋‚ด์ฃผ๊ธฐ๋กœ ํ–ˆ์—ˆ๋‹ค.

@GetMapping(value = "")
@Operation(summary = "ํŒ€ํ”Œ ์ƒ์„ธ ์ •๋ณด ์กฐํšŒ", description = "ํŒ€ํ”Œ ์ƒ์„ธ ์ •๋ณด ์กฐํšŒ API ์ž…๋‹ˆ๋‹ค.")
public CommonResponse<GetTeamDetailDto> getTeamDetail(@AuthUser User authUser,
                                                      @Valid @RequestParam("teamId") Long teamId) {
    log.info("[api-get] ํŒ€ ์ƒ์„ธ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ");

    GetTeamDetailDto teamDetail = teamsService.getTeamDetail(authUser, teamId);
    return CommonResponse.onSuccess(HttpStatus.OK.value(), teamDetail);
}

@PostMapping(value = "")
@Operation(summary = "ํŒ€ํ”Œ ์ƒ์„ฑ", description = "ํŒ€ํ”Œ ์ƒ์„ฑ API ์ž…๋‹ˆ๋‹ค.\n"
        + "stage๊ฐ€ ์—†์–ด๋„ []๋ฅผ ๋ณด๋‚ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค.")
public CommonResponse<PostTeamResDto> postTeam(@AuthUser User authUser,
                                               @Valid @RequestBody PostTeamDto postTeamDto) {
    log.info("[api-post] ํŒ€ ์ƒ์„ฑ");

    PostTeamResDto postTeamResDto = teamsService.createTeam(authUser, postTeamDto);
    return CommonResponse.onSuccess(HttpStatus.CREATED.value(), postTeamResDto);
}

Common Response ์•Œ์•„๋ณด๊ธฐ โ†“

๋”๋ณด๊ธฐ
@Getter
public class CommonResponse<T> {
    @JsonProperty("status")
    private int code;

    @JsonProperty("isSuccess")
    private boolean success;

    @JsonProperty("message")
    private String message;

    @JsonProperty("data")
    @JsonInclude(JsonInclude.Include.NON_NULL)
    private T data;

    @Builder
    public CommonResponse(int code, boolean success, String message, T data) {
        this.code = code;
        this.success = success;
        this.message = message;
        this.data = data;
    }

    public static <T> CommonResponse<T> onSuccess(int code) {
        return CommonResponse.<T>builder()
                .code(code)
                .success(true)
                .message("์š”์ฒญ์— ์„ฑ๊ณตํ•˜์˜€์Šต๋‹ˆ๋‹ค.")
                .data(null)
                .build();
    }

    public static <T> CommonResponse<T> onSuccess(int code, T data) {
        return CommonResponse.<T>builder()
                .code(code)
                .success(true)
                .message("์š”์ฒญ์— ์„ฑ๊ณตํ•˜์˜€์Šต๋‹ˆ๋‹ค.")
                .data(data)
                .build();
    }
}

์œ„ ์ฝ”๋“œ๋Š” ์ดˆ๊ธฐ ๊ตฌํ˜„ํ•œ ์ปจํŠธ๋กค๋Ÿฌ์ด๋‹ค. 

์ฝ”๋“œ๋ฅผ ๋ณผ ๊ฒฝ์šฐ return ๋ถ€๋ถ„์— custom response๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ถ€๋ถ„์ด ์ค‘๋ณต๋˜์–ด์žˆ๋‹ค.

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ณ ์ž ํ•œ๋‹ค.


๐Ÿ“Œ ResponseBodyAdvice ์ปค์Šคํ…€

๐Ÿ”ต ResponseBodyAdvice ๋ž€?

 ResponseBodyAdvice๋Š” controller์—์„œ ์ฒ˜๋ฆฌ๋œ ์‘๋‹ต์„ ๊ฐ€๋กœ์ฑ„์„œ, ํ•ด๋‹น ์‘๋‹ต์„ ๋ณ€ํ™˜ํ•˜๊ฑฐ๋‚˜ ์ถ”๊ฐ€์ ์ธ ์ฒ˜๋ฆฌ๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ค€๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์ฒ˜๋ฆฌ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐ์ž‘ํ•˜๊ณ , ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ณด๋‚ด๊ธฐ ์ „์— ๋ณ€ํ™˜ํ•˜๊ฑฐ๋‚˜, ์ถ”๊ฐ€์ ์ธ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์‚ฝ์ž…ํ•  ์ˆ˜ ์žˆ๋‹ค.

public interface ResponseBodyAdvice<T> {
	boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);

	@Nullable
	T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType,
			Class<? extends HttpMessageConverter<?>> selectedConverterType,
			ServerHttpRequest request, ServerHttpResponse response);

}

์ด๋Ÿฐ ์‹์œผ๋กœ interface๊ฐ€ ๊ตฌ์„ฑ๋˜์–ด์žˆ๋Š”๋ฐ 

์‰ฝ๊ฒŒ ๋งํ•˜๋ฉด, support๋Š” controller ์ž‘์—…์ด ๋๋‚œ response๋ฅผ beforeBodyWrite ๋ฉ”์„œ๋“œ์— ๋ณด๋‚ผ์ง€ ํŒ๋‹จํ•˜๋Š” ์—ญํ• ์„ ํ•˜๊ณ , beforeBodyWrite ๋ฉ”์„œ๋“œ๋Š” ์‹ค์ œ ์‚ฌ์šฉ์ž๊ฐ€ ์›ํ•˜๋Š” body์˜ ๊ฐ’์„ ๊ต์ฒด ๋˜๋Š” response์— ํ—ค๋” ์ •๋ณด๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด๋ฅผ ์ด์šฉํ•˜์—ฌ controller ๋‹จ์—์„œ ๋ฐ์ดํ„ฐ๋งŒ ๋ฐ˜ํ™˜ํ•˜๋ฉด, response๊ฐ€ Common Response๋กœ ๋ณ€ํ™˜๋˜๋„๋ก ๊ตฌํ˜„ํ•  ๊ฒƒ์ด๋‹ค.

๐Ÿ”ต ์ ์šฉํ•˜๊ธฐ

๐Ÿ”น์ „์ฒด์ฝ”๋“œ

์ „์ฒด ์ฝ”๋“œ๋Š” ์ด๋Ÿฌํ•˜๋‹ค

๋”๋ณด๊ธฐ
@RestControllerAdvice
public class SuccessResponseAdvice implements ResponseBodyAdvice<Object> {
    private final String[] SwaggerPatterns = {
            "swagger",
            "/v2/api-docs"
    };

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @Nullable
    public Object beforeBodyWrite(
            Object body,
            MethodParameter returnType,
            MediaType selectedContentType,
            Class selectedConverterType,
            ServerHttpRequest request,
            ServerHttpResponse response){
        HttpServletResponse servletResponse =
                ((ServletServerHttpResponse) response).getServletResponse();
        HttpServletRequest servletRequest =
                ((ServletServerHttpRequest) request).getServletRequest();
        HttpMessageConverter

        // ์Šค์›จ๊ฑฐ์ผ ๊ฒฝ์šฐ ๋ฆฌ์Šคํฐ์Šค ์ฒ˜๋ฆฌ ์•ˆํ•˜๋„๋ก
        for (String swaggerPattern : SwaggerPatterns) {
            if (servletRequest.getRequestURI().contains(swaggerPattern))
                return body;
        }

        HttpStatus resolve = HttpStatus.resolve(servletResponse.getStatus());

        if (resolve == null)
            return body;

        if (resolve.is2xxSuccessful())
            return CommonResponse.onSuccess(statusProvider(servletRequest.getMethod()), body);


        return body;
    }

    private int statusProvider(String method) {
        if (method.equals("POST"))
            return 201;
        if (method.equals("DELETE"))
            return 204;
        return 200;
    }
}

 

ResponseBodyAdvice์˜ ๊ตฌํ˜„์ฒด๋กœ ๋งŒ๋“  ํ›„, ๋‘ ํ•จ์ˆ˜๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋”ฉ ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ•˜์˜€๋‹ค.

๐Ÿ”นsupport

๋จผ์ € support ํ•จ์ˆ˜๋ถ€ํ„ฐ ๋ณด๊ฒ ๋‹ค

@Override
public boolean supports(MethodParameter returnType, Class converterType) {
    return true;
}

์š”์ฒญ์ด ์–ด๋А ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์˜ค๋Š”๊ฐ€์— ์ƒ๊ด€์—†์ด beforeBodyWrite ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰์‹œ์ผœ response๋ฅผ ์ปค์Šคํ…€ํ•  ๊ณ„ํš์ด๊ธฐ ๋•Œ๋ฌธ์—, ๋‹ค๋ฅธ ๋ณ€๊ฒฝ, ํ•„ํ„ฐ๋ง ์—†์–ด true๋งŒ ๋ฐ˜ํ™˜ํ–ˆ๋‹ค.

๊ทธ๋Ÿฌ๋ฏ€๋กœ beforeBodyWrite๊ฐ€ ์‹คํ–‰๋œ๋‹ค.

๐Ÿ”นbeforeBodyWrite

@Nullable
public Object beforeBodyWrite(
        Object body,
        MethodParameter returnType,
        MediaType selectedContentType,
        Class selectedConverterType,
        ServerHttpRequest request,
        ServerHttpResponse response){
    HttpServletResponse servletResponse =
            ((ServletServerHttpResponse) response).getServletResponse();
    HttpServletRequest servletRequest =
            ((ServletServerHttpRequest) request).getServletRequest();

    // ์Šค์›จ๊ฑฐ์ผ ๊ฒฝ์šฐ ๋ฆฌ์Šคํฐ์Šค ์ฒ˜๋ฆฌ ์•ˆํ•˜๋„๋ก
    for (String swaggerPattern : SwaggerPatterns) {
        if (servletRequest.getRequestURI().contains(swaggerPattern))
            return body;
    }

    HttpStatus resolve = HttpStatus.resolve(servletResponse.getStatus());

    if (resolve == null)
        return body;

    if (resolve.is2xxSuccessful())
        return CommonResponse.onSuccess(statusProvider(servletRequest.getMethod()), body);


    return body;
}

url์ด ์Šค์›จ๊ฑฐ ์—ฐ๊ด€๋œ url์ด๊ฑฐ๋‚˜, http status๊ฐ€ 200์ด response์˜ ๊ฒฝ์šฐ, custom response๋กœ ๋ณ€ํ™˜ํ•˜์ง€ ์•Š๋„๋ก ๋กœ์ง์„ ๊ตฌ์„ฑํ–ˆ๋‹ค.

๋งŒ์•ฝ ์ด ์กฐ๊ฑด์„ ํ†ต๊ณผํ•œ๋‹ค๋ฉด CommonResponse ๋ผ๋Š” custom response๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ response๊ฐ€ ์ „๋‹ฌ๋˜๋„๋ก ๊ตฌ์„ฑํ–ˆ๋‹ค.

๐Ÿ”น์Šค์›จ๊ฑฐ url ํŒ๋ณ„ ํ›„ response ๋ถ„๋ฆฌ

์ดˆ๊ธฐ์— ์Šค์›จ๊ฑฐ url์„ ๋”ฐ๋กœ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š์•˜์„ ๋•Œ, ์œ„ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

์œ„ ๋กœ์ง์— ๋”ฐ๋ฅด๋ฉด, ๋ชจ๋“  success response ์ฆ‰, 200๋ฒˆ๋Œ€ response๋Š” common response๋กœ ๋ณ€ํ™˜๋œ๋‹ค.

์ •ํ™•ํ•œ ์ด์œ ๋Š” ๋ชจ๋ฅด๊ฒ ์œผ๋‚˜, ์ด ๋ถ€๋ถ„์—์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒƒ ๊ฐ™๋‹ค. 

๊ทธ๋ž˜์„œ ์Šค์›จ๊ฑฐ url์„ ๊ฐ€์ง„ response์ผ ๊ฒฝ์šฐ, ๋”ฐ๋กœ ์ฒ˜๋ฆฌํ•˜์—ฌ body๋ฅผ return ํ•ด์ฃผ๋Š” ๋กœ์ง์„ ์ถ”๊ฐ€ํ–ˆ๋‹ค.

private final String[] SwaggerPatterns = {
        "swagger",
        "/v2/api-docs"
};

...

// ์Šค์›จ๊ฑฐ์ผ ๊ฒฝ์šฐ ๋ฆฌ์Šคํฐ์Šค ์ฒ˜๋ฆฌ ์•ˆํ•˜๋„๋ก
for (String swaggerPattern : SwaggerPatterns) {
    if (servletRequest.getRequestURI().contains(swaggerPattern))
        return body;
}

๐Ÿ”นHttp Method๋กœ Http status code ๋ถ„๋ฅ˜ํ•˜์—ฌ response ์ƒ์„ฑํ•˜๊ธฐ

์ดˆ๊ธฐ ์ฝ”๋“œ์—์„œ common response๋ฅผ ์ƒ์„ฑํ•  ๊ฒฝ์šฐ, ๋ชจ๋‘ http status code๊ฐ€ 200๋ฒˆ์œผ๋กœ ์ƒ์„ฑ๋˜์—ˆ๋‹ค. 

ํ•˜์ง€๋งŒ ๊ธฐ์กด ์ฝ”๋“œ response๋Š” 201, 204 ๋“ฑ 200 ์ด์™ธ ๋‹ค์–‘ํ•œ http status code๋ฅผ ๋ฐ˜ํ™˜ํ–ˆ๋‹ค.

๊ทธ๋ž˜์„œ ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ Http Method์— ๋”ฐ๋ผ http status code๋ฅผ ๋ถ„๋ฅ˜ํ•˜์—ฌ common response๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋„๋ก ํ–ˆ๋‹ค.

private int statusProvider(String method) {
    if (method.equals("POST"))
        return 201;
    if (method.equals("DELETE"))
        return 204;
    return 200;
}

์ด ๋ฐฉ๋ฒ•์ด ์ •ํ™•ํžˆ ๋งž๋Š” ๋ฐฉ๋ฒ•์ธ์ง€๋Š” ์˜๋ฌธ์ด๋‹ค...

์ข‹์€ ๋ฐฉ๋ฒ• ์•„์‹œ๋Š” ๋ถ„ ์žˆ์œผ์‹œ๋ฉด, ๋Œ“๊ธ€ ๋‚จ๊ฒจ์ฃผ์„ธ์š”....