SpringBoot

springboot로 Rest api 만들기(5) API 인터페이스 및 결과 데이터 구조 설계

pepega 2021. 10. 22. 11:43

전체 소스코드

https://github.com/GHGHGHKO/Springboot/tree/main/pepega_chapter_5

 

GitHub - GHGHGHKO/Springboot: 블로그에 업로드 된 소스코드

블로그에 업로드 된 소스코드. Contribute to GHGHGHKO/Springboot development by creating an account on GitHub.

github.com

 

이전 포스팅에는 swagger를 활용하여 API 문서를 자동화하는 웹페이지를 제작했다.

이번 포스팅에는

API 서버 개발을 본격적으로 진행해 보기 위해

현재 API 인터페이스 및 결과 데이터의 구조를 살펴보고

확장 가능한 형태로 설계할 것이다.

API는 제공 대상이 클라이언트 APP이나 WEB 개발자이다. (프론트앤드)

한 번 배포되고 공유한 API는 구조를 쉽게 바꿀 수 없기 때문에

처음부터 효율적이로 확장 가능한 형태로 모델을 설계하고 시작하는게 좋다.

그래서 다음과 같이 HttpMethod를 사용하고

Restful 한 API를 만들기 위해 몇 가지 규칙을 적용 할 것이다.

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

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

HttpMethod를 제공한다.

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

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

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

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

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

2. 리소스에 Mapping된 주소 체계를 정형화 한다.

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

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

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

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

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

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

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

3. 결과 데이터의 구조를 표준화하여 정의한다.

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

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

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

 

구현1. 결과 모델의 정의

com.example.pepega 하위에 model.reponse package를 생성한다. (model.reponse 그대로 치면 된다.)

생성한 package 하위에 결과를 담을 3개의 모델을 생성한다.

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

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

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

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

package com.example.pepega.model.response;

import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class CommonResult {

    @ApiModelProperty(value = "응답 성공여부 : true/false")
    private boolean success;

    @ApiModelProperty(value = "응답 코드 번호 : >= 0 정상, < 0 비정상")
    private int code;

    @ApiModelProperty(value = "응답 메시지")
    private String msg;
}

 

결과가 단일 건인 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;
}

 

구현2. 결과 모델을 처리할 Service 정의

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

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

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

 

package com.example.pepega.service;

import com.example.pepega.model.response.CommonResult;
import com.example.pepega.model.response.ListResult;
import com.example.pepega.model.response.SingleResult;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import javax.management.monitor.CounterMonitor;
import java.util.List;


@Service // 해당 클래스가 Service임을 명시한다.

public class ResponseService {

    @Getter
    @RequiredArgsConstructor
    @AllArgsConstructor
    // enum으로 API 요청 결과에 대한 code, message를 정의한다.
    public enum CommonResponse {
        SUCCESS(0, "성공하였습니다. HappyPepe! XD"),
        FAIL(-1, "실패하였습니다. SadPepe :(");

        private int code;
        private String msg;
    }

    // 단일건 결과를 처리하는 메소드
    public <T> SingleResult<T> getSingleResult(T data) {
        SingleResult<T> result = new SingleResult<>();
        result.setData(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 응답 결과를 알려주는 클래스
        CommonResult result = new CommonResult();
        setSuccessResult(result);
        return result;
    }

    // 실패 결과만 처리하는 메소드
    public CommonResult getFailResult() {
        CommonResult result = new CommonResult();
        result.setSuccess(false); // setSuccess : 응답 성공 여부 (true/false)
        result.setCode(CommonResponse.FAIL.getCode()); // setCode : 응답 코드 번호 >= 0 정상, < 0 비정상
        result.setMsg(CommonResponse.FAIL.getMsg()); // setMsg 응답 메시지
        return result;
    }

    // 결과 모델에 API 요청 성공 데이터를 세팅해주는 메소드
    private void setSuccessResult(CommonResult result) {
        result.setSuccess(true);
        result.setCode(CommonResponse.SUCCESS.getCode());
        result.setMsg(CommonResponse.SUCCESS.getMsg());
    }
}

구현3. HttpMethod와 정형화된 주소체계로 Controller 수정

resource의 사용 목적에 따라

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

결과 데이터의 형태에 따라

단일건 처리는 SingleResult()

다중건 처리는 ListResult()

API 처리 성공 결과만 필요한 경우 getSuccessResult()

를 사용한다.

 

package com.example.pepega.controller.v1;


import com.example.pepega.entity.User;
import com.example.pepega.model.response.CommonResult;
import com.example.pepega.model.response.ListResult;
import com.example.pepega.model.response.SingleResult;
import com.example.pepega.repo.UserJpaRepo;
import com.example.pepega.service.ResponseService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@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()를 이용하여 결과를 출력
    }
}

이제 서버를 실행해본다.

이후

http://localhost:8080/swagger-ui.html

위 URL에 접속한다.

 

POST로 RickAstley 씨가 등록되었다.

PUT으로 msrl 5 였던 히맨이

샌즈로 바뀌었다.

DELETE로 RickAstley 씨가 삭제되었다.

GET으로 msrl가 5인 Comic Sans를 출력하였다.

GET으로 모든 사용자들을 출력해봤다.

이번 포스팅에는 API 성공에 대한 내용만 진행했다.

다음 포스팅에는 실패 시의 ExceptionHandling과 결과 Message 처리애 대한 내용을 살펴보겠다.

출처

https://github.com/codej99/SpringRestApi

 

GitHub - codej99/SpringRestApi: SpringBoot2, SpringSecurity, JWT, Stateless Restful API

SpringBoot2, SpringSecurity, JWT, Stateless Restful API - GitHub - codej99/SpringRestApi: SpringBoot2, SpringSecurity, JWT, Stateless Restful API

github.com