본문 바로가기

Back-end/SpringBoot

[SpringBoot] REST API 구조 설계 및 SpringBoot 응답 클래스 만들기(feat. SingleResult, ListResult)

리소스의 사용목적에 따라 Http method를 구분해서 사용

Http 프로토콜은 여러 가지 사용목적에 따라

HttpMethod를 제공한다.

다음 4가지 HttpMethod를 상황에 맞게 API 구현에 사용할 것이다.

GET - 서버에 주어진 리소스의 정보를 요청한다. (읽기)

POST - 서버에 리소스를 제출한다. (쓰기)

PUT - 서버에 리소스를 제출한다. POST와 달리 리소스 갱신 시 사용한다. 수정)

DELETE - 서버에 주어진 리소스를 삭제 요청한다. (삭제 시)

 

 

리소스에 Mapping된 주소 체계를 정형화

주소 체계는 아래처럼 정형화된 구조로 구성한다.

HttpMethod를 통해 리소스의 사용 목적을 판단하는 것이 핵심이다.

GET /v1/user/{msrl} - 회원 msrl에 해당하는 정보를 조회한다.

GET /v1/users - 회원 리스트를 조회한다.

POST /v1/user - 신규 회원정보를 입력한다.

PUT /v1/user - 기존 회원의 정보를 수정한다.

DELETE /v1/user/{msrl} - msrl로 기존 회원의 정보를 삭제한다.

 

 

 

결과 데이터의 구조를 표준화하여 정의

결과 데이터는 아래의 샘플처럼

결과 데이터 + API 요청 결과 데이터로 구성한다.

// 기존 USER 정보
{
    "msrl": 1,
    "uid": "pepe@sadfrog.com",
    "name": "페페"
}
// 표준화한 USER 정보
{
  "data": {
    "msrl": 1,
    "uid": "pepe@sadfrog.com",
    "name": "페페"
  },
  "success": true
  "code": 0,
  "message": "성공하였습니다."
}

 

 

API의 실행 결과를 담는 공통 모델

 

API의 처리 상태 및 메시지를 내려주는 데이터로 구성된다.

success는 API의 성공, 실패 여부를 나타내고

code, msg는 해당 상황에서의 응답 코드와 응답 메시지를 나타낸다.

 

@Getter
@Setter
public class CommonResult implements Serializable {
    //응답 성공여부 : true/false
    private boolean status;

    //응답 코드 번호 : >= 0 정상, < 0 비정상
    private int resultCode;

    //httpStatus
    private HttpStatus httpStatus;

    //응답 메시지
    private String resultMsg;
}

 

결과가 단일 건인 API를 담는 모델

 

Generic Interface에 <T>를 지정하여

어떤 형태의 값도 넣을 수 있도록 구현한다.

또한, CommonResult를 상속받아 API 요청 결과도 같이 출력된다.

 

package com.example.pepega.model.response;

@Getter
@Setter
public class SingleResult<T> extends CommonResult {
    // CommonResult를 상속받아 API 요청 결과도 같이 출력

    private T data;
}

 

 

결과가 여러 건인 API를 담는 모델

API 결과가 다중 건인 경우에 대한 데이터 모델이다.

결과 필드를 List 형태로 선언하고

Generic Interface에 <T>를 지정하여

어떤 형태의 List 값도 넣을 수 있도록 구현한다.

또한, CommonResult를 상속받아 API 요청 결과도 같이 출력한다.

 

package com.example.pepega.model.response;

import lombok.Getter;
import lombok.Setter;

import java.util.List;

@Getter
@Setter
public class ListResult<T> extends CommonResult {
    // List 형태로 여러 데이터를 받음
    // CommonResult를 상속받아 API 요청 결과도 같이 출력
    
    private List<T> list;
}

 

 

 

결과 모델을 처리할 Service 정의

 

결과 모델에 데이터를 넣어주는 역할을 하는 Service를 정의한다.

com.example.pepega 하위에 service package를 생성한다.

service package 안에 ResponseService 클래스를 생성한다.

 

@Service
public class ResponseService {
    // enum으로 api 요청 결과에 대한 code, message를 정의합니다.
    public enum CommonResponse {
        SUCCESS(0, "Success"),
        FAIL(-1, "Fail");
        int code;
        String msg;
        CommonResponse(int code, String msg) {
            this.code = code;
            this.msg = msg;
        }
        public int getCode() {
            return code;
        }
        public String getMsg() {
            return msg;
        }
    }
    // 단일건 결과를 처리하는 메소드
    public <T> SingleResult<T> getSingleResult(T data) {
        SingleResult<T> result = new SingleResult<>();
        result.setResult(data);
        setSuccessResult(result);
        return result;
    }
    // 다중건 결과를 처리하는 메소드
    public <T> ListResult<T> getListResult(List<T> list) {
        ListResult<T> result = new ListResult<>();
        result.setList(list);
        setSuccessResult(result);
        return result;
    }

    // 성공 결과만 처리하는 메소드
    public CommonResult getSuccessResult() {
        CommonResult result = new CommonResult();
        setSuccessResult(result);
        return result;
    }
    // 실패 결과만 처리하는 메소드
    public CommonResult getFailResult() {
        CommonResult result = new CommonResult();
        result.setStatus(false);
        result.setResultCode(CommonResponse.FAIL.getCode());
        result.setResultMsg(CommonResponse.FAIL.getMsg());
        result.setHttpStatus(HttpStatus.INTERNAL_SERVER_ERROR);
        return result;
    }
    // 결과 모델에 api 요청 성공 데이터를 세팅해주는 메소드
    private void setSuccessResult(CommonResult result) {
        result.setStatus(true);
        result.setResultCode(CommonResponse.SUCCESS.getCode());
        result.setResultMsg(CommonResponse.SUCCESS.getMsg());
        result.setHttpStatus(HttpStatus.OK);
    }

    // 실패 결과만 처리하는 메소드
    public CommonResult getAccessDeniedResult(HttpServletResponse response) {
        CommonResult result = new CommonResult();
        result.setStatus(false);
        result.setResultCode(CommonResponse.FAIL.getCode());
        result.setResultMsg(CommonResponse.FAIL.getMsg());
        result.setHttpStatus(HttpStatus.valueOf(response.getStatus()));
        return result;
    }
}

 

HttpMethod와 정형화된 주소체계로 Controller 수정

 

resource의 사용 목적에 따라

GetMapping, PostMapping, PutMapping, DeleteMapping을 사용한다.

결과 데이터의 형태에 따라

단일건 처리는 SingleResult()

다중건 처리는 ListResult()

API 처리 성공 결과만 필요한 경우 getSuccessResult()를 사용한다.

 

@Api(tags = {"1. User"}) // UserController를 대표하는 최상단 타이틀 영역에 표시될 값 세팅
@RequiredArgsConstructor // class 내부의 final 객체는 Constructor Injection 수행, @Autowired도 가능
@RestController // 결과를 JSON으로 도출
@RequestMapping(value = "/v1") // api resource를 버전별로 관리, /v1 을 모든 리소스 주소에 적용
public class UserController {

    private final UserJpaRepo userJpaRepo; // Jpa를 활용한 CRUD 쿼리 가능
    private final ResponseService responseService; // 결과를 처리하는 Service

    @ApiOperation(value = "회원 리스트 조회", notes = "모든 회원을 조회한다.") // 각각의 resource에 제목과 설명 표시
    @GetMapping(value = "/users") // user 테이블의 모든 정보를 읽어옴
    public ListResult<User> findAllUser() { // 데이터가 1개 이상일 수 있기에 List<User>로 선언
        return responseService.getListResult(userJpaRepo.findAll()); // JPA를 사용하면 CRUD에 대해 설정 없이 쿼리 사용 가능 (select * from user 와 같음)
        //결과 데이터가 여러개인 경우 getListResult 활용
    }

    @ApiOperation(value = "회원 단건 조회", notes = "msrl로 회원을 조회한다.")
    @GetMapping(value = "/user/{msrl}")
    public SingleResult<User> findUserById(@ApiParam(value = "회원ID", required = true) @PathVariable long msrl) {
        return responseService.getSingleResult(userJpaRepo.findById(msrl).orElse(null));
        // 결과 데이터가 단일건인 경우 getSingleResult를 이용하여 결과를 출력
    }

    @ApiOperation(value = "회원 입력", notes = "회원을 입력한다.")
    @PostMapping(value = "/user") // user 테이블에 데이터를 입력하는 부분 insert into user (msrl, name, uid) values (null, ?, ?) 와 같음
    public SingleResult<User> save(@ApiParam(value = "회원아이디", required = true) @RequestParam String uid, // 파라미터의 설명을 보여주기 위해 세팅
                                   @ApiParam(value = "회원이름", required = true) @RequestParam String name) {
        User user = User.builder()
                .uid(uid) // User 클래스에서 만들어진 변수 uid, name
                .name(name)
                .build();

        return responseService.getSingleResult(userJpaRepo.save(user));
    }

    @ApiOperation(value = "회원 수정", notes = "회원정보를 수정한다.")
    @PutMapping(value = "/user")
    public SingleResult<User> modify(
            @ApiParam(value = "회원번호", required = true) @RequestParam long msrl,
            @ApiParam(value = "회원아이디", required = true) @RequestParam String uid,
            @ApiParam(value = "회원이름", required = true) @RequestParam String name) {
        User user = User.builder()
                .msrl(msrl)
                .uid(uid)
                .name(name)
                .build();

        return responseService.getSingleResult(userJpaRepo.save(user));
    }

    @ApiOperation(value = "회원 삭제", notes = "msrl로 회원정보를 삭제한다.")
    @DeleteMapping(value = "/user/{msrl}")
    public CommonResult delete (
            @ApiParam(value = "회원정보", required = true) @PathVariable long msrl ) {
        userJpaRepo.deleteById(msrl); // deleteById id를 받아 delete query 실행
        return responseService.getSuccessResult();
        // 성공 결과 정보만 필요한 경우 getSuccessResult()를 이용하여 결과를 출력
    }
}