티스토리 뷰

스프링 부트 REST API WEB 프로젝트

깃헙 링크

https://github.com/choiwoonsik/springboot_RestApi_App_Project/tree/main/restApiSpringBootApp

수행 목록

  1. 환경구성 및 helloworld 출력
  2. H2 DB 연동
  3. Swagger API 문서 연동
  4. REST API 설계
  5. RestControllerAdvice를 이용한 통합 예외 처리
  6. Entity - DTO 분리
  7. MessageSource를 이용해 예외 메시지 다국화
  8. JPA Aduting을 이용해 객체 생성시간/수정시간 적용
  9. 스프링 시큐리티 + Jwt를 이용해서 인증 및 권한 체크
  10. 스프링 시큐리티 AuthenticationEntryPoint, AccessDenied로 인증 및 인가 예외처리
  11. Jwt AccessToken + RefreshToken으로 보안성과 사용자 편의성 고도화하기
  12. JUnit Test (단위 테스트)
  13. JUnit Test (통합 테스트)
  14. OAuth 2.0 정리
  15. OAuth 2.0 카카오 로그인 part.1 Authorization code + Token 발급
  16. OAuth 2.0 카카오 로그인 part.2 토큰으로 회원 가입 / 로그인
  17. OAuth 2.0 카카오 로그인 테스트 검증
  18. 환경별 설정을 위해서profile 분리하기 

API를 구현할 때 확장가능하도록 Rest API로 구현하기 위해서 아래와 같이 단계적으로 구현을 해보자.

API 설계시 꼭 지켜야할 점은

  • Entity를 그대로 반환해서는 안된다. (DTO를 사용)
  • 리소스의 사용에 맞게 HTTP Method를 사용해야한다.
  • 반환 시 배열로 바로 반환하지 않고 Json 형식에 맞게 data로 넣어줘야 한다. (확장성을 위해)

 

1. 리소스의 사용 목적에 맞게 HTTP Method 정의

User 엔티티에 대해 먼저 http 메소드를 정의하자.

 

조회

  • GET /v1/users : 모든 회원 목록을 조회
  • GET /v1/user/{param} : param을 기준으로 회원을 조회
    • id, email, name 등에 따라서 단일 객체, 복수 객체가 반환된다.

등록

  • POST /v1/user : 회원 등록

수정

  • PUT /v1/user : 회원 정보 수정

삭제

  • DELETE /v1/user{userId} : 회원 삭제

2.  결과 데이터의 구조를 표준화해서 정의

기존 User 정보를 바탕으로 응답 데이터 구조를 짜면된다

기존 User 데이터
{
    "userId": 1,
    "email": "운식@이메일.com",
    "name": "최똥꼬"
}
User 정보를 바탕으로 응답 데이터 구성 (데이터 + api 요청 결과 데이터)
{
    data: {
      "userId": 1,
      "email": "운식@이메일.com",
      "name": "최똥꼬"
    },
    "success": ture,
    "code": 0,
    "message": "성공하였습니다."
}

응답 모델 3가지 구현

com.restApi.restApiSpringBootApp 하위에 model.response 폴더를 만들고 응답을 담을 3가지 모델을 만들자.

공통 응답 모델

전달될 데이터와 별개로 API의 처리여부, 상태, 메시지가 담긴 데이터이다. 이 응답은 다른 모든 응답이 상속받아서 갖도록 한다.
@Getter
@Setter
public class CommonResult {

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

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

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

 

단일 응답 모델

API 반환값이 단일 객체일 경우 해당 모델로 처리한다. 공통 응답 모델을 상속받았으므로 API 응답 관련 정보도 포함되어있다. 또한 타입을 Generic타입인 <T>로 선언해서 User Entity가 아닌 다른 엔티티에도 적용이 가능하도록 설계했다.
@Getter
@Setter
public class SingleResult<T> extends CommonResult {
    private T data;
}

다중 응답 모델

@Getter
@Setter
public class ListResult<T> extends CommonResult {
    private List<T> data;
}

 

3. API가 반환한 모델을 처리할 Service 구현

결과 모델에 데이터를 넣어주는 service를 구현한다. ResponseService 클래스를 service 패키지를 생성해서 안에 생성한다.

 

1. 공통 응답 모델을 Success / Fail로 처리하기위한 Enum 클래스를 생성한다.

@Getter
@AllArgsConstructor
public enum CommonResponse {
    SUCCESS(0, "성공하였습니다."),
    FAIL(-1, "실패하였습니다.");

    private int code;
    private String msg;
}

 

 

2. 유형별로 ResponseService를 구현해준다.

@Service
public class ResponseService {

    // 단일건 결과 처리 메소드
    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 result = new CommonResult();
        setSuccessResult(result);
        return result;
    }

    // 실패 결과만 처리
    public CommonResult getFailResult() {
        CommonResult result = new CommonResult();
        setFailResult(result);
        return result;
    }

    // API 요청 성공 시 응답 모델을 성공 데이터로 세팅
    private void setSuccessResult(CommonResult result) {
        result.setSuccess(true);
        result.setCode(CommonResponse.SUCCESS.getCode());
        result.setMsg(CommonResponse.SUCCESS.getMsg());
    }

    // API 요청 실패 시 응답 모델을 실패 데이터로 세팅
    private void setFailResult(CommonResult result) {
        result.setSuccess(false);
        result.setCode(CommonResponse.FAIL.getCode());
        result.setMsg(CommonResponse.FAIL.getMsg());
    }
}
  • 단일 응답, 복수 응답 별로 나눠서 API 응답을 받고, 공통 응답 부분을 성공 여부에 따라 T/F 로 처리해준다.
  • 성공여부, 실패 여부만을 반환하는 메소드도 선언해준다.
    • 회원 삭제 시 삭제가 성공하면 성공응답을 반환하는 용도로 사용할 수 있다.

 

4. Http Method와 정형화된 주소체계로 Controller 구현

위에서 정의한 GET, POST, PUT, DELETE에 맞춰서 Mapping 테이블을 완성한다.

@Api(tags = {"1. User"})
@RequiredArgsConstructor
@RestController
@RequestMapping("/v1")
public class UserController {

    private final UserJpaRepo userJpaRepo;
    private final ResponseService responseService;

    @ApiOperation(value = "회원 단건 검색", notes = "userId로 회원을 조회합니다.")
    @GetMapping("/user/{userId}")
    public SingleResult<User> findUserByKey(@ApiParam(value = "회원 ID", required = true) @PathVariable Long userId) {
        return responseService
                .getSingleResult(userJpaRepo.findById(userId).orElse(null));
    }

    @ApiOperation(value = "회원 목록 조회", notes = "모든 회원을 조회합니다.")
    @GetMapping("/users")
    public ListResult<User> findAllUser() {
        return responseService
                .getListResult(userJpaRepo.findAll());
    }

    @ApiOperation(value = "회원 등록", notes = "회원을 등록합니다.")
    @PostMapping("/user")
    public SingleResult<User> save(@ApiParam(value = "회원 이메일", required = true) @RequestParam String email,
                                   @ApiParam(value = "회원 이름", required = true) @RequestParam String name) {
        User user = User.builder()
                .email(email)
                .name(name)
                .build();
        return responseService.getSingleResult(userJpaRepo.save(user));
    }

    @ApiOperation(value = "회원 수정", notes = "회원 정보를 수정합니다.")
    @PutMapping("/user")
    public SingleResult<User> modify(@ApiParam(value = "회원 아이디", required = true) @RequestParam Long userId,
                                     @ApiParam(value = "회원 이메일", required = true) @RequestParam String email,
                                     @ApiParam(value = "회원 이름", required = true) @RequestParam String name) {
        User user = User.builder()
                .userId(userId)
                .email(email)
                .name(name)
                .build();
        return responseService.getSingleResult(userJpaRepo.save(user));
    }

    @ApiOperation(value = "회원 삭제", notes = "회원을 삭제합니다.")
    @DeleteMapping("/user/{userId}")
    public CommonResult delete(@ApiParam(value = "회원 아이디", required = true) @PathVariable Long userId) {
        userJpaRepo.deleteById(userId);
        return responseService.getSuccessResult();
    }
}
  • 현재 DTO가 아닌 User Entity를 그대로 반환하고 있는 구조이므로 필히 변경이 필요하다.

 

결과

 

반응형
Comments
반응형
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday