Skip to content

Latest commit

ย 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
ย 
ย 

README.md

API ์‘๋‹ต ํ†ต์ผ

API ์‘๋‹ต ํ†ต์ผ

  • ๋ชจ๋“  API์˜ ์‘๋‹ต์ด ํ†ต์ผ๋˜์ง€ ์•Š์œผ๋ฉด ํ”„๋ก ํŠธ์˜ ์ž…์žฅ์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ ํž˜๋“ค๋‹ค.
  • ๋”ฐ๋ผ์„œ ํ”„๋กœ์ ํŠธ๋งˆ๋‹ค API๋ฅผ ํ†ต์ผํ•˜์—ฌ์•ผ ํ•œ๋‹ค.
  • ๋ณดํ†ต์˜ ๊ฒฝ์šฐ ์•„๋ž˜ 4๊ฐ€์ง€์˜ ์ •๋ณด๋ฅผ ํฌํ•จํ•˜๋„๋ก ์ž‘์„ฑํ•œ๋‹ค.
    • isSuccess : Boolean ํƒ€์ž…์˜ ์„ฑ๊ณต ์—ฌ๋ถ€
    • code : HTTP ์ƒํƒœ ์ฝ”๋“œ ์™ธ์— ๋” ์„ธ๋ถ€์ ์ธ ๊ฒฐ๊ณผ
    • message : code์— ์ถ”๊ฐ€์ ์œผ๋กœ ์–ด๋–ค ๊ฒฐ๊ณผ์ธ์ง€๋ฅผ ์•Œ๋ ค์ฃผ๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ
    • result : ์‘๋‹ต์œผ๋กœ ํ•„์š”ํ•œ ๋˜ ๋‹ค๋ฅธ json ์ •๋ณด
  • ํ”„๋กœ์ ํŠธ ํŒŒ์ผ์—์„œ ๊ตฌํ˜„ ์‹œ ์•„๋ž˜์™€ ๊ฐ™์€ ๊ตฌ์กฐ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋•Œ (I)๋Š” ์ธํ„ฐํŽ˜์ด์Šค (E)๋Š” ์ด๋„˜ ํด๋ž˜์Šค๋ฅผ ์˜๋ฏธํ•œ๋‹ค.
๐Ÿ“com.example.umc9th
โ”œโ”€ ๐Ÿ“domain
โ”‚   โ””โ”€ ๐Ÿ“member
โ”‚        โ”œโ”€ ๐Ÿ“controller
โ”‚        โ”œโ”€ ๐Ÿ“converter
โ”‚        โ”œโ”€ ๐Ÿ“dto
โ”‚        โ”‚   โ”œโ”€ ๐Ÿ“req
โ”‚        โ”‚   โ”‚    โ””โ”€ MemberReqDto
โ”‚        โ”‚   โ””โ”€ ๐Ÿ“res
โ”‚        โ”‚        โ””โ”€ MemberResDto
โ”‚        โ””โ”€ ๐Ÿ“service
โ”‚            โ”œโ”€ ๐Ÿ“command
โ”‚            โ””โ”€ ๐Ÿ“query
โ””โ”€ ๐Ÿ“global
    โ””โ”€ ๐Ÿ“apiPayload
         โ”œโ”€ ๐Ÿ“code
         โ”‚   โ”œโ”€ (I) BaseErrorCode
         โ”‚   โ”œโ”€ (I) BaseSuccessCode
         โ”‚   โ”œโ”€ (E) GeneralErrorCode
         โ”‚   โ””โ”€ (E) GeneralSuccessCode
         โ””โ”€ ApiResponse
  • ์ถ”๊ฐ€์ ์œผ๋กœ ์„œ๋น„์Šค๋ฅผ ์ž‘์„ฑํ•  ๋•Œ๋Š” ์•„๋ž˜ ๋‘ ๊ฐ€์ง€ ํŒŒ์ผ์— ๋‚˜๋ˆ  ์ž‘์„ฑํ•œ๋‹ค.
    • Query : GET ์š”์ฒญ์— ๋Œ€ํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๋“ค
    • Command : ์ด์™ธ ์š”์ฒญ์— ๋Œ€ํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๋“ค

๐Ÿ“code

code๋Š” ์‘๋‹ต ์ฝ”๋“œ๋ฅผ ๋‹ด๋Š” ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค. ํŠนํžˆ ๋ฒ ์ด์Šค ์ธํ„ฐํŽ˜์ด์Šค์—์„œ๋Š” ์ตœ์†Œํ•œ์˜ ๊ตฌํ˜„ ๋ฉ”์†Œ๋“œ๋ฅผ ์ •ํ•œ๋‹ค.

public interface BaseErrorCode {

    HttpStatus getStatus();
    String getCode();
    String getMessage();
}

๋ฒ ์ด์Šค ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ ์ด๋„˜ ๊ตฌํ˜„ ํด๋ž˜์Šค์—์„œ๋Š” ์ƒํƒœ ์ฝ”์Šค, ์ƒ์„ธ ์ฝ”๋“œ, ๋ฉ”์‹œ์ง€๋ฅผ ์ •ํ•œ๋‹ค.

@Getter // ๋กฌ๋ณต์„ ํ†ตํ•ด ์ธํ„ฐํŽ˜์ด์Šค์—์„œ ์ •์˜ํ•œ ๊ฒŒํ„ฐ๋“ค์„ ์‹ค์ œ๋กœ ๊ตฌํ˜„ํ•จ
@AllArgsConstructor
public enum GeneralErrorCode implements BaseErrorCode{

  BAD_REQUEST(HttpStatus.BAD_REQUEST,
                "COMMON400_1",
                "์ž˜๋ชป๋œ ์š”์ฒญ์ž…๋‹ˆ๋‹ค."),
  UNAUTHORIZED(HttpStatus.UNAUTHORIZED,
                "AUTH401_1",
                "์ธ์ฆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค."),
  FORBIDDEN(HttpStatus.FORBIDDEN,
                "AUTH403_1",
                "์š”์ฒญ์ด ๊ฑฐ๋ถ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค."),
  NOT_FOUND(HttpStatus.NOT_FOUND,
                "COMMON404_1",
                "์š”์ฒญํ•œ ๋ฆฌ์†Œ์Šค๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."),
  ;

  private final HttpStatus status;
  private final String code;
  private final String message;
 }

ApiResponse

  • ์‘๋‹ต์„ ์œ„ํ•œ ๊ณตํ†ต API ํด๋ž˜์Šค๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์ด๋•Œ result์— ์–ด๋–ค ๊ฐ’์ด ๋‹ด๊ธฐ๊ฒŒ ๋  ์ง€ ์•Œ ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ์ œ๋„ค๋ฆญ ํƒ€์ž…์œผ๋กœ ์ž‘์„ฑํ•œ๋‹ค.
@Getter
@AllArgsConstructor
@JsonPropertyOrder({"isSuccess", "code", "message", "result"})
public class ApiResponse<T> {

    @JsonProperty("isSuccess")
    private final Boolean isSuccess;

    @JsonProperty("code")
    private final String code;

    @JsonProperty("message")
    private final String message;

    @JsonProperty("result")
    private T result;

    // ์„ฑ๊ณตํ•œ ๊ฒฝ์šฐ (๊ฒฐ๊ณผ ํฌํ•จ)
    public static <T> ApiResponse<T> onSuccess(BaseSuccessCode code, T result) {
        return new ApiResponse<>(true, code.getCode(), code.getMessage(), result);
    }

    // ์‹คํŒจํ•œ ๊ฒฝ์šฐ (๊ฒฐ๊ณผ ํฌํ•จ)
    public static <T> ApiResponse<T> onFailure(BaseErrorCode code, T result) {
        return new ApiResponse<>(false, code.getCode(), code.getMessage(), result);
    }
}

ํ•ด๋‹น ๊ณผ์ •๋“ค์„ ๋๋‚ด๋ฉด API ์‘๋‹ต์„ ์œ„ํ•œ ์ตœ์†Œํ•œ์˜ ํ†ต์ผ์ด ๋งˆ๋ฌด๋ฆฌ ๋œ๋‹ค.


DTO

  • ์‘๋‹ต API์˜ ๊ฒฐ๊ณผ๋กœ ๋‹ด๊ธฐ ์œ„ํ•œ ์ •๋ณด๋“ค๊ณผ ํ”„๋ก ํŠธ์—์„œ ์ „์†ก๋ฐ›๋Š” ์ •๋ณด๋“ค์„ ํฌ์žฅํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” DTO๋“ค์„ ์ •์˜ํ•œ๋‹ค.

TestResDTO

  • DTO๋“ค์€ MemberResDto, ReviewResDto ๋“ฑ๋“ฑ ํฐ ์นดํ…Œ๊ณ ๋ฆฌ์—์„œ public ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค๊ณ  ๊ทธ ์•ˆ์—์„œ ์„ธ๋ถ€์ ์œผ๋กœ static ํด๋ž˜์Šค๋ฅผ ์ •์˜ํ•ด ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.
  • DTO๋Š” ์•„์ฃผ ๋งŽ์€ ๊ณณ์—์„œ ์‚ฌ์šฉ๋˜๊ธฐ ๋•Œ๋ฌธ์— ๋งค๋ฒˆ ํด๋ž˜์Šค๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ํ•˜๋‚˜์˜ ํด๋ž˜์Šค ๋‚ด์—์„œ static ํด๋ž˜์Šค๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ด ๋งค์šฐ ํšจ์œจ์ ์ด๋‹ค.
  • ๋˜ํ•œ MemberResDto ํด๋ž˜์Šค ์•ˆ์—์„œ ์„ ์–ธ๋˜๋Š” ๊ฒƒ์ด๋ฏ€๋กœ ๋ชจ๋“  DTO์— ์ผ์ผ์ด Member๋ผ๋Š” ๋„๋ฉ”์ธ์˜ ์ด๋ฆ„์„ ์ž…๋ ฅํ•ด์ค„ ํ•„์š”๊ฐ€ ์—†๋‹ค.
  • ์‚ฌ์šฉ ์‹œ์—๋„ MemberResDto.changeNickname์ฒ˜๋Ÿผ ์™ธ๋ถ€ ํด๋ž˜์Šค ์ด๋ฆ„๊ณผ ๋‚ด๋ถ€ ํด๋ž˜์Šค ์ด๋ฆ„์„ ํ•จ๊ป˜ ์ ์–ด ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
public class MemberResDto {   // ํฐ ๋ฌถ์Œ์œผ๋กœ ํด๋ž˜์Šค ์ƒ์„ฑ

    @Builder
    @Getter
    public static class ChangeNickname {   // ๋‚ด๋ถ€์—์„œ public static์œผ๋กœ ์„ ์–ธํ•œ ๋’ค ์‚ฌ์šฉ 
        private String nickname;
    }
}
  • ์‘๋‹ต DTO์˜ ๊ฒฝ์šฐ, ๋ฐฑ์•ค๋“œ ์ธก์—์„œ ๋‚ด์šฉ์„ ์ฑ„์›Œ์•ผ ํ•˜๋ฏ€๋กœ ๋นŒํ„ฐ ํŒจํ„ด์„ ์‚ฌ์šฉํ•œ๋‹ค.
  • ์š”์ฒญ DTO์˜ ๊ฒฝ์šฐ, ํ”„๋ก ํŠธ์—์„œ ์ธก์—์„œ ๋งŒ๋“ค์–ด์ง„ ๊ฐ์ฒด์˜ ์ •๋ณด๋ฅผ ํ•œ ๋ฒˆ์— ๋ฐ›์•„์˜ค๋Š” ๊ฒƒ์ด๋ฏ€๋กœ ๋นŒ๋” ํŒจํ„ด์„ ๊ฑฐ์˜ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.

Converter

  • ๊ฐ์ฒด๋ฅผ DTO๋กœ ๋ฐ”๊พธ๋Š” ํด๋ž˜์Šค.
  • ์—ฌ๋Ÿฌ ๊ฐ์ฒด์™€ ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ๋ฐ›์•„ ํ•„์š”ํ•œ ์ •๋ณด๋งŒ์„ ๊ฑธ๋Ÿฌ DTO๋กœ ํฌ์žฅํ•ด์ค€๋‹ค.
public class MemberConverter {
    
    // ๋ฉค๋ฒ„ ๊ฐ์ฒด๋ฅผ ๋ฐ›์•„ ๊ด€๋ จ DTO๋กœ ๋ณ€ํ™˜ํ•ด์คŒ
    public static MemberResDto.ChangeNickname toNicknameDTO(Member member) {
        return MemberResDto.ChangeNickname.builder()
                .nickname(member.getNickname())
                .build();
    }
}

์ปจํŠธ๋กค๋Ÿฌ

Controller

  • @Controller : ์ „ํ†ต์ ์ธ MVC์—์„œ View(ํ™”๋ฉด, HTML)๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ
  • @RestController
    • RESTful API๋ฅผ ๊ตฌ์ถ•ํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.
    • @Controller + @ResponseBody๊ฐ€ ํ•ฉ์ณ์ง„ ์–ด๋…ธํ…Œ์ด์…˜์ด๋‹ค.
    • ๋ฐ์ดํ„ฐ(JSON)๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์ด ์ฃผ ๋ชฉ์ ์ด๋‹ค.
@RestController
@RequiredArgsConstructor
@RequestMapping("/members")    // ์ปจํŠธ๋กค๋Ÿฌ ์ „์ฒด ๊ฒฝ๋กœ ์„ค์ •
public class TestController {

    @PatchMapping("/me")
    public ApiResponse<MemberResDto.ChangeNickname> changeNickname(
        @RequestBody @Valid MemberReqDto.ChangeNickname req) throws Exception {
        /**
        * ์„œ๋น„์Šค ํด๋ž˜์Šค์—์„œ ๋‹‰๋„ค์ž„ ๋ณ€๊ฒฝ ๋กœ์ง์ด ์ด๋ฃจ์–ด์ง
        * MemberConverter.toNicknameDTO(member)๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ๋จ!!
        */
        MemberResDto.ChangeNickname res = memberService.changeNickname(req);    // ์„œ๋น„์Šค ํด๋ž˜์Šค ์‹คํ–‰ ํ›„ DTO ๋ฐ˜ํ™˜
        GeneralSuccessCode code = GeneralSuccessCode.OK;                        // ์‘๋‹ต ์ฝ”๋“œ ์ •์˜
        return ApiResponse.onSuccess(
                code,
                res
        );
    }
}

์ตœ์ข…์ ์œผ๋กœ ํ”„๋ก ํŠธ์—์„œ PATCH /members/me ๊ฒฝ๋กœ๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด ์„ฑ๊ณต ๋ฉ”์‹œ์ง€์™€ ํ•จ๊ป˜ ๋ณ€๊ฒฝ๋œ ๋‹‰๋„ค์ž„์ด ๋Œ์•„์˜ค๊ฒŒ ๋œ๋‹ค.

๐Ÿ’ก ์‹ค์ œ ๋ฐ˜ํ™˜ ํƒ€์ž…์€?

  • ์‹ค๋ฌด์—์„œ๋Š” HTTP ์‘๋‹ต ์ž์ฒด๋ฅผ ์ œ์–ดํ•˜๋Š”(HTTP ์ƒํƒœ ์ฝ”๋“œ, ํ—ค๋” ๋“ฑ) ResponseEntity๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๋‹ค.
  • ๊ฐ€์žฅ ๋ณดํŽธ์ ์ธ ๋ฐ˜ํ™˜ ํƒ€์ž…์€ ResponseEntity<ApiResponse>์ด๋‹ค.
  • ์ฆ‰, Entity โ†’ DTO โ†’ ApiResponse โ†’ ResponseEntity๋กœ ์ด์–ด์ง€๋Š” ํ๋ฆ„์ด ๋œ๋‹ค!
  • ๊ท€์ฐฎ์€ ๋“ฏ ๋ณด์—ฌ๋„ ์ด ๋ชจ๋“  ๊ณผ์ •์„ ๊ฑฐ์ณ์•ผ ์˜ˆ์ธก ๊ฐ€๋Šฅํ•˜๊ณ  ์•ˆ์ •์ ์ธ API๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.
    • HTTP ์ƒํƒœ ์ฝ”๋“œ(200, 400 ๋“ฑ) : ๊ธฐ๊ณ„/์ธํ”„๋ผ๋ฅผ ์œ„ํ•œ ๊ฒƒ์ด๋‹ค. ๋ธŒ๋ผ์šฐ์ €, ์บ์‹œ, ๋ชจ๋‹ˆํ„ฐ๋ง ํˆด, ๋„คํŠธ์›Œํฌ ์žฅ๋น„ ๋“ฑ์ด ์‚ฌ์šฉํ•œ๋‹ค.
    • ApiResponse ๋‚ด๋ถ€ ์ฝ”๋“œ : ์‚ฌ๋žŒ/์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋กœ์ง์„ ์œ„ํ•œ ๊ฒƒ์ด๋‹ค. ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž, ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณด์—ฌ์ง€๊ฒŒ ๋  ๋ฉ”์‹œ์ง€์—์„œ ์‚ฌ์šฉ๋œ๋‹ค.

์˜ˆ์™ธ ์ฒ˜๋ฆฌ

์˜ˆ์™ธ ์ฒ˜๋ฆฌ

  • ํ”„๋กœ์ ํŠธ ๋ ˆ๋ฒจ์˜ ์˜ˆ์™ธ์™€ ๋„๋ฉ”์ธ ๋ ˆ๋ฒจ์˜ ์˜ˆ์™ธ๋ฅผ ๋ณ„๊ฐœ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.
  • ์‘๋‹ต์ด ํ†ต์ผ๋œ ์˜ˆ์™ธ๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋ชจ๋“  ๋„๋ฉ”์ธ ์˜ˆ์™ธ๋ฅผ ๋ชจ์•„์ค„ ๊ฐ์ฒด๊ฐ€ ํ•„์š”ํ•˜๋‹ค.
  • ๊ทธ๊ฒƒ์„ ์—๋Ÿฌ ํ—จ๋“ค๋Ÿฌ๋ผ ๋ถ€๋ฅธ๋‹ค.
  • api์™€ ์˜ˆ์™ธ์ฒ˜๋ฆฌ์— ๊ด€๋ จ๋œ ์ „์ฒด์ ์ธ ํŒจํ‚ค์ง€ ๊ตฌ์กฐ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.
๐Ÿ“com.example.umc9th
โ”œโ”€ ๐Ÿ“domain
โ”‚   โ””โ”€ ๐Ÿ“review
โ”‚        โ”œโ”€ ๐Ÿ“controller
โ”‚        โ”œโ”€ ๐Ÿ“converter
โ”‚        โ”œโ”€ ๐Ÿ“dto
โ”‚        โ”œโ”€ ๐Ÿ“exception
โ”‚        โ”‚    โ””โ”€ ReviewExceptio
โ”‚        โ””โ”€ ๐Ÿ“service
โ”‚            โ”œโ”€ ๐Ÿ“command
โ”‚            โ”‚    โ”œโ”€ (I) ReviewCommandService
โ”‚            โ”‚    โ””โ”€ ReviewCommandServiceImpl
โ”‚            โ””โ”€ ๐Ÿ“query
โ”‚                 โ”œโ”€ (I) ReviewQueryService
โ”‚                 โ””โ”€ ReviewQueryServiceImpl
โ””โ”€ ๐Ÿ“global
    โ””โ”€ ๐Ÿ“apiPayload
         โ”œโ”€ ๐Ÿ“code
         โ”‚   โ”œโ”€ (I) BaseErrorCode
         โ”‚   โ””โ”€ (E) GeneralErrorCode
         โ”œโ”€ ๐Ÿ“exception
         โ”‚   โ””โ”€ GeneralException
         โ”œโ”€ ๐Ÿ“handler
         โ”‚   โ””โ”€ GeneralExceptionAdvice
         โ””โ”€ ApiResponse

์˜ˆ์™ธ ํด๋ž˜์Šค

ํ”„๋กœ์ ํŠธ ๋ ˆ๋ฒจ์˜ ์˜ˆ์™ธ

RuntimeException์„ ์ƒ์†๋ฐ›์•„ ์ž‘์„ฑํ•œ๋‹ค.

@Getter
@AllArgsConstructor
public class GeneralException extends RuntimeException {

    private final BaseErrorCode code;
}

๋„๋ฉ”์ธ ๋ ˆ๋ฒจ์˜ ์˜ˆ์™ธ

ํ”„๋กœ์ ํŠธ ๋ ˆ๋ฒจ์˜ ์˜ˆ์™ธ๋ฅผ ์ƒ์†๋ฐ›์•„ ์ž‘์„ฑํ•œ๋‹ค.

public class TestException extends GeneralException {
    public TestException(BaseErrorCode code) {
        super(code);
    }
}

์—๋Ÿฌ ํ•ธ๋“ค๋Ÿฌ

  • ์ˆ˜๋งŽ์€ ์œ„์น˜์—์„œ ํ„ฐ์ง„ ์˜ˆ์™ธ๋“ค์˜ ์‘๋‹ต์„ ํ†ต์ผํ•  ํ†ต์ผ ๊ฐ์ฒด๊ฐ€ ํ•„์š”ํ•˜๋‹ค.
  • ์ด๋ฅผ ์˜ˆ์™ธ ํ•ธ๋“ค๋Ÿฌ๋ผ ๋ถ€๋ฅธ๋‹ค.
  • ์—๋Ÿฌ ํ•ธ๋“ค๋Ÿฌ ์ฝ”๋“œ์˜ ์ž‘์„ฑ์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.
@RestControllerAdvice
public class GeneralExceptionAdvice {

    // ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์ปค์Šคํ…€ ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌ
    @ExceptionHandler(GeneralException.class)
    public ResponseEntity<ApiResponse<Void>> handleException(
            GeneralException ex
    ) {

        return ResponseEntity.status(ex.getCode().getStatus())
                .body(ApiResponse.onFailure(
                                ex.getCode(),
                                null
                        )
                );
    }

    // ๊ทธ ์™ธ์˜ ์ •์˜๋˜์ง€ ์•Š์€ ๋ชจ๋“  ์˜ˆ์™ธ ์ฒ˜๋ฆฌ
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ApiResponse<String>> handleException(
            Exception ex
    ) {

        BaseErrorCode code = GeneralErrorCode.INTERNAL_SERVER_ERROR;
        return ResponseEntity.status(code.getStatus())
                .body(ApiResponse.onFailure(
                                code,
                                ex.getMessage()
                        )
                );
    }
}
  • ๊ธฐ๋ณธ์ ์ธ ์›๋ฆฌ๋Š” ์ •์˜ํ•œ ์˜ˆ์™ธ๋ฅผ ๊ฐ์ง€ํ•˜๊ณ  ๋ฏธ๋ฆฌ ์ •์˜ํ•ด๋‘” ์—๋Ÿฌ ํ•ธ๋“ค๋Ÿฌ ๋กœ์ง์„ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
  • ๋”ฐ๋ผ์„œ ๋„๋ฉ”์ธ ์˜ˆ์™ธ๋Š” ํ”„๋กœ์ ํŠธ ์˜ˆ์™ธ๋ฅผ ์ƒ์†ํ•˜๋Š” ๊ตฌ์กฐ์ด๊ณ , ์ด๋Ÿฌํ•œ ์˜ˆ์™ธ๋ฅผ ๊ฐ์ง€ํ•˜๋Š” ๊ฒƒ์€ ์—๋Ÿฌ ํ•ธ๋“ค๋Ÿฌ์˜ ์—ญํ• ์ด๋‹ค.

์—๋Ÿฌ ์ฝ”๋“œ๋ฅผ ์ •์˜ํ•˜๋Š” Enum

  • ์—๋Ÿฌ ์ฝ”๋“œ์— ๊ทœ์น™์„ ์ •ํ•ด๋‘๋ฉด ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž์™€ ์†Œํ†ต์ด ํŽธํ•ด์ง„๋‹ค.
_INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "COMMON000", "์„œ๋ฒ„ ์—๋Ÿฌ, ๊ด€๋ฆฌ์ž์—๊ฒŒ ๋ฌธ์˜ ๋ฐ”๋ž๋‹ˆ๋‹ค."),
  • ์œ„์˜ ์—๋Ÿฌ ์ฝ”๋“œ์—์„œ HttpStatus.INTERNAL_SERVER_ERROR๋งŒ ๋ณด๊ณ ๋„ ์˜ค๋ฅ˜์˜ ํฐ ํ‹€์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.
  • ๋‘ ๋ฒˆ์งธ ์ธ์ž์ธ code๋ฅผ ๋ณด๊ณ  ๋” ์ž์„ธํ•œ ์˜ค๋ฅ˜๋ฅผ ์•Œ ์ˆ˜ ์žˆ๋‹ค.
  • COMMON000 : common ์—๋Ÿฌ
  • MEMBER4001 : ๋ฉค๋ฒ„ ๊ด€๋ จ ์—๋Ÿฌ + 400๋ฒˆ๋Œ€(ํด๋ผ์ด์–ธํŠธ ์˜ค๋ฅ˜) + ๊ทธ ์ค‘์—์„œ๋„ 1๋ฒˆ ์—๋Ÿฌ
  • ํ•ด๋‹น ๊ทœ์น™์„ ์ ์šฉ์— ์•„๋ž˜์™€ ๊ฐ™์€ Enum์„ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.
// Member Error
MEMBER_NOT_FOUND(HttpStatus.BAD_REQUEST, "MEMBER4001", "์‚ฌ์šฉ์ž๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."),
NICKNAME_NOT_EXIST(HttpStatus.BAD_REQUEST, "MEMBER4002", "๋‹‰๋„ค์ž„์€ ํ•„์ˆ˜ ์ž…๋‹ˆ๋‹ค."),

// Article Error
ARTICLE_NOT_FOUND(HttpStatus.NOT_FOUND, "ARTICLE4001", "๊ฒŒ์‹œ๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.");

์˜ˆ์™ธ ์ฒ˜๋ฆฌ์˜ ํ…Œ์ŠคํŠธ

  • ์ปจํŠธ๋กค๋Ÿฌ ๋‚ด์— ์ „์—ญ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊ธฐ(@RestControllerAdvice)๋ฅผ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์œ„ํ•œ ๊ฒฝ๋กœ๋ฅผ ํ•˜๋‚˜ ์ƒ์„ฑํ•œ๋‹ค.
    • GET /temp/exception
    • ์ด๋Š” ๋‚ด๊ฐ€ ์ž‘์„ฑํ•œ ์ „์—ญ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊ธฐ๊ฐ€ ์ œ๋Œ€๋กœ ์ž‘๋™ํ•˜๋Š”์ง€๋ฅผ ํ™•์ธํ•ด๋ณด๊ธฐ ์œ„ํ•œ ํ…Œ์ŠคํŠธ ๊ฒฝ๋กœ์ด๋‹ค.
  • ์ฆ‰, ์˜ˆ์™ธ์ฒ˜๋ฆฌ๊ฐ€ ์ œ๋Œ€๋กœ ๋˜๋Š”์ง€๋ฅผ ํ™•์ธํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด GET /temp/exception๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๋ณด๋ฉด ๋œ๋‹ค.
  • ์ด๋•Œ ์ฟผ๋ฆฌ์ŠคํŠธ๋ง flag๋ฅผ ์‚ฌ์šฉํ•ด ์˜ค๋กœ์ง€ flag=1์ผ ๋•Œ๋งŒ ์˜ˆ์™ธ๊ฐ€ ํ„ฐ์ง€๋„๋ก ํ•œ๋‹ค.
  • ์ด๋Š” ํ•ด๋‹น ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๊ฒฝ๋กœ๊ฐ€ ์›๋ž˜๋Š” ์ •์ƒ์ ์ธ ๊ฒฝ๋กœ์ด๋ฉฐ ์˜ˆ์™ธ๋ฅผ ํ„ฐํŠธ๋ฆฌ๋ฉด ํ•ด๋‹น ์˜ˆ์™ธ๊ฐ€ ์ œ๋Œ€๋กœ ์‹คํŒจ api์— ๋‹ด๊ฒจ ์ „๋‹ฌ๋˜๋Š”์ง€๋ฅผ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•จ์ด๋‹ค.

์ปจํŠธ๋กค๋Ÿฌ

@RequestParam๋Š” ์ฟผ๋ฆฌ ์ŠคํŠธ๋ง์„ ๋ฐ›์•„์˜ค๊ธฐ ์œ„ํ•œ ์–ด๋…ธํ…Œ์ด์…˜์ด๋‹ค.

@RestController
@RequestMapping("/temp")
@RequiredArgsConstructor
public class TempRestController {

		...
		
    // ์ „์—ญ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊ธฐ์˜ ํ…Œ์ŠคํŠธ ๊ฒฝ๋กœ
    @GetMapping("/exception")
    public ApiResponse<TestResDTO.Exception> exception(@RequestParam Long flag) {
        return null;
    }
}

์„œ๋น„์Šค

Service๋ฅผ ์ž‘์„ฑ ํ•  ๋•Œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ๊ทœ์น™์„ ๋”ฐ๋ฅธ๋‹ค.

  1. ์กฐํšŒ์— ๋Œ€ํ•œ ์š”์ฒญ์€ query ํด๋”๋กœ, ์ด์™ธ์˜ ์š”์ฒญ์€ command ํด๋”๋กœ ๊ตฌ๋ถ„ํ•œ๋‹ค.
  2. ์„œ๋น„์Šค๋ฅผ ๋งŒ๋“ค ๊ฒฝ์šฐ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋จผ์ € ๋‘๊ณ  ์ด๋ฅผ ๊ตฌ์ฒดํ™” ํ•œ๋‹ค.
  • ex) TempQueryService ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋จผ์ € ๋งŒ๋“ค๊ณ  ์ด์— ๋Œ€ํ•œ Impl ๊ตฌ์ฒดํ™” ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ ๋‹ค.
  1. ์ปจํŠธ๋กค๋Ÿฌ๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์˜์กดํ•˜๋ฉฐ ์‹ค์ œ ์ธํ„ฐํŽ˜์ด์Šค์— ๋Œ€ํ•œ ๊ตฌ์ฒดํ™” ํด๋ž˜์Šค๋Š” ์Šคํ”„๋ง๋ถ€ํŠธ์˜ ์˜์กด์„ฑ ์ฃผ์ž…์„ ์ด์šฉํ•œ๋‹ค!

๐Ÿ’ก ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ 

์œ„์˜ ๊ฒฝ์šฐ ์ธํ„ฐํŽ˜์ด์Šค์™€ ๊ตฌํ˜„์ฒด๋Š” ์ผ๋Œ€์ผ๋กœ ๋งคํ•‘๋œ๋‹ค. ๊ทธ๋Ÿผ ์™œ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ• ๊นŒ?

  1. ์˜์กด์„ฑ ์—ญ์ „ ์›์น™
  • ์ปจํŠธ๋กค๋Ÿฌ๋Š” ๊ตฌ์ฒด์ ์ธ ๊ตฌํ˜„ ํด๋ž˜์Šค๊ฐ€ ์–ด๋–ค ์‹์œผ๋กœ ๋™์ž‘ํ•˜๋Š”์ง€ ์•Œ ํ•„์š”๊ฐ€ ์—†๋‹ค.
  • ์ธํ„ฐํŽ˜์ด์Šค์— ์ •์˜๋œ ์ธ์ž๊ฐ’๊ณผ ๋ฐ˜ํ™˜ ํƒ€์ž…์— ๋Œ€ํ•œ ์ •๋ณด๋งŒ์„ ์•Œ๊ณ  ์žˆ๋‹ค.
  1. ํ…Œ์ŠคํŠธ ์šฉ์ด์„ฑ
  • ๋งŒ์•ฝ ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ ๊ตฌ์ฒด์ ์ธ ๊ตฌํ˜„์ฒด์— ์ง์ ‘ ์˜์กดํ•œ๋‹ค๋ฉด ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ํ…Œ์ŠคํŠธํ•  ๋•Œ Service๊ฐ€ ์˜์กดํ•˜๋Š” ๋ ˆํŒŒ์ง€ํ† ๋ฆฌ๋‚˜ DB๊ฐ€ ๋ชจ๋‘ ํ•„์š”ํ•˜๊ฒŒ ๋œ๋‹ค.
  • ๊ทธ๋Ÿฌ๋‚˜ ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ ์ธํ„ฐํŽ˜์ด์Šค์— ์˜์กดํ•˜๊ฒŒ ๋˜๋ฉด ํ…Œ์ŠคํŠธ ์‹œ ๊ฐ€์งœ ๊ตฌํ˜„์ฒด๋ฅผ ์‰ฝ๊ฒŒ ์ฃผ์ž…ํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ’ก ํ•˜๋‚˜์˜ ์ธํ„ฐํŽ˜์ด์Šค, ์—ฌ๋Ÿฌ ๊ตฌํ˜„์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ

  • ๋งŒ์•ฝ PaymentService๋ผ๋Š” ๊ฒฐ์ œ ์„œ๋น„์Šค ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ๊ฒฐ์žฌ ๋ฐฉ์‹์— ๋”ฐ๋ผ ๊ตฌํ˜„์ฒด๊ฐ€ ๋‚˜๋‰  ์ˆ˜ ์žˆ๋‹ค.
    • KakaoPaymentService
    • TossPaymentService
    • NaverPaymentService
  • ์ด๋•Œ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์‚ฌ์šฉ์ž์˜ ๊ฒฐ์žฌ ์ˆ˜๋‹จ์— ๋Œ€ํ•œ ์ •๋ณด๊ฐ€ ๋“ค์–ด์˜ค๋ฉด ์ ์ ˆํ•œ ๊ตฌํ˜„์ฒด๋ฅผ ๋™์ ์œผ๋กœ ์„ ํƒํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

์„œ๋น„์Šค ์ธํ„ฐํŽ˜์ด์Šค

public interface TestQueryService {
    void checkFlag(Long flag);
}

์„œ๋น„์Šค ์‹ค์ œ ๊ตฌํ˜„

checkFlag ๋ฉ”์†Œ๋“œ๋Š” flag๊ฐ€ 1์ธ ๊ฒฝ์šฐ์—๋งŒ ์˜ˆ์™ธ๋ฅผ ํ„ฐํŠธ๋ฆฐ๋‹ค.

@Service
@RequiredArgsConstructor
public class TestQueryServiceImpl implements TestQueryService {

    @Override
    public void checkFlag(Long flag){
        if (flag == 1){
            throw new TestException(TestErrorCode.TEST_EXCEPTION);
        }
    }
}

์ตœ์ข…์ ์œผ๋กœ ์™„์„ฑ๋œ Get /temp/exception ๊ฒฝ๋กœ ๋ฉ”์†Œ๋“œ

@GetMapping("/exception")
public ApiResponse<TestResDTO.Exception> exception(
        @RequestParam Long flag
) {

    testQueryService.checkFlag(flag);   // flag๊ฐ€ 1์ด๋ผ๋ฉด ์ด ๋ถ€๋ถ„์—์„œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค!!

    GeneralSuccessCode code = GeneralSuccessCode.OK;
    return ApiResponse.onSuccess(code, TestConverter.toExceptionDTO("This is Test!"));
}

๐ŸŽฏ ํ•ต์‹ฌ ํ‚ค์›Œ๋“œ

@RestControllerAdvice

  • Spring์—์„œ ์ „์—ญ ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ๋ชจ๋“  ์ปจํŠธ๋กค๋Ÿฌ์˜ ์˜ˆ์™ธ๋ฅผ ํ•œ ๊ณณ์—์„œ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.
  • @ExceptionHandler์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ๋˜์–ด ๊ณตํ†ต๋œ ์—๋Ÿฌ ์‘๋‹ต ํ˜•์‹์„ ์ œ๊ณตํ•œ๋‹ค.
  • @ControllerAdvice + @ResponseBody์˜ ์กฐํ•ฉ๊ณผ ๋™์ผํ•œ ๊ธฐ๋Šฅ์„ ํ•œ๋‹ค.

Lombok

  • ๋ฐ˜๋ณต๋˜๋Š” ์ฝ”๋“œ(์ƒ์„ฑ์ž, getter/setter, toString ๋“ฑ)๋ฅผ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•ด์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋‹ค.
  • @Getter, @Setter, @Builder, @AllArgsConstructor ๋“ฑ ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ์ฝ”๋“œ ๊ฐ€๋…์„ฑ์„ ๋†’์ธ๋‹ค.
  • ์ปดํŒŒ์ผ ์‹œ์ ์— ์‹ค์ œ ์ฝ”๋“œ๊ฐ€ ์ถ”๊ฐ€๋˜๋ฏ€๋กœ ์‹คํ–‰ ์†๋„์—๋Š” ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๋Š”๋‹ค.

DTO: public static class vs record

  • public static class๋Š” Lombok๊ณผ ํ•จ๊ป˜ ์“ฐ์—ฌ ์œ ์—ฐํ•˜๊ณ  ๊ณ„์ธต์  ๊ตฌ์กฐ๋ฅผ ๋งŒ๋“ค๊ธฐ ์ข‹๋‹ค.
  • record๋Š” ์ž๋ฐ” 16+์—์„œ ๋„์ž…๋œ ๋ถˆ๋ณ€(immutable) ๋ฐ์ดํ„ฐ ์ „๋‹ฌ์šฉ ํด๋ž˜์Šค์ด๋ฉฐ ์ฝ”๋“œ๊ฐ€ ๋งค์šฐ ๊ฐ„๊ฒฐํ•˜๋‹ค.
  • ๊ทธ๋Ÿฌ๋‚˜ record๋Š” ํ•„๋“œ ์ˆ˜์ •์ด๋‚˜ ์ƒ์†์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฏ€๋กœ ๋‹จ์ˆœ DTO์—๋งŒ ์ ํ•ฉํ•˜๋‹ค.