Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

justsicklife

Spring boot Rest Api Web 프로젝트 따라하기 (2) Rest API 설계 본문

프로젝트

Spring boot Rest Api Web 프로젝트 따라하기 (2) Rest API 설계

구슬탈출 2024. 4. 27. 06:47

깃허브 링크

 

GitHub - justsicklife/springboot_RestApi_Web_Project

Contribute to justsicklife/springboot_RestApi_Web_Project development by creating an account on GitHub.

github.com

 

따라 만든 링크

 

spring boot REST API Web 프로젝트 (4) - Http 메소드를 기준으로 API 설계 (Controller, Service)

스프링 부트 REST API WEB 프로젝트 깃헙 링크 https://github.com/choiwoonsik/springboot_RestApi_App_Project/tree/main/restApiSpringBootApp 수행 목록 환경구성 및 helloworld 출력 H2 DB 연동 Swagger API 문서 연동 REST API 설계 Re

ws-pace.tistory.com

 

API를 구현할 때 확장 가능하도록 REST API를 구현하기 위해서는 아래 단계를 따르자

  1. Entity를 그대로 반환해서는 안된다 (dto) 를 사용
  2. 리소스의 맞게 HTTP METHOD 를 사용해야 한다.
  3. 반환 시 객체로 반환하지 말고 JSON 형태로 맞게 data로 넣어줘야 한다 (확장성을 위해)

조회

  • GET /v1/users : 모든 회원을 조회
  • GET /v1/users/{}

등록 

POST /v1/user : 회원등록

 

수정

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

 

삭제

DELETE /v1/user : 회원 삭제

 

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

기존 User 정보를 바탕으로 응답 데이터 구조를 만들자

기존 User 데이터
{
    "userId": 1,
    "email": "abc@gmail.com"
    "name": "구슬탈출"
}
기존 User 데이터
{
	"data": {
        "userId": 1,
        "email": "abc@gmail.com"
        "name": "구슬탈출"
	},
    "success":true,
    "code": 0,
    "message": "성공하였습니다."
}

응답 모델 3가지 구현

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

 

공통 응답 모델

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

    @Schema(name = "응답 성공 여부 T/F")
    private boolean success;

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

    @Schema(name = "응답 메시지")
    private String msg;
}

단일 응답 모델

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

다중 응답 모델

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

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. Mapper, userService,mapper.xml 파일  수정하기

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTO Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.restApi.restApiSpring.demo.repository.UserMapper">
    <select id="findById" resultType="com.restApi.restApiSpring.demo.dto.User">
        SELECT * FROM user where userId = #{userId}
    </select>

    <select id="findByName" resultType="com.restApi.restApiSpring.demo.dto.User">
        SELECT * FROM user where name = #{name}
    </select>

    <select id="findAll" resultType="com.restApi.restApiSpring.demo.dto.User">
        SELECT * FROM user
    </select>

    <select id="findByEmail" resultType="com.restApi.restApiSpring.demo.dto.User">
        SELECT * FROM user where email = #{email}
    </select>
    
    <insert id="save" parameterType="com.restApi.restApiSpring.demo.dto.User">
        insert into user values(USER_ID_SEQ.nextval,#{name},#{email})
    </insert>

    <update id="updateById" parameterType="com.restApi.restApiSpring.demo.dto.User">
        update user set
        userId = #{userId}, email = #{email}, name = #{name}
        where userId = #{userId}
    </update>

    <delete id="deleteById" parameterType="Long">
        delete from user where userId = #{userId}
    </delete>
</mapper>

userService

public interface UserService {

    public Optional<User> findById(Long userId);

    public List<User> findByName(String name);

    public User findByEmail(String email);

    public int save(User user);

    public List<User> findAll();

    int updateById(User user);

    int deleteById(Long userId);

}

userServiceImpl

@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService{

    final UserMapper userMapper;

    @Override
    public Optional<User> findById(Long userId) {
        return userMapper.findById(userId);
    }

    @Override
    public List<User> findByName(String name) {
        return userMapper.findByName(name);
    }

    @Override
    public User findByEmail(String email) {
        return userMapper.findByEmail(email);
    }

    @Override
    public int save(User user) {
        return userMapper.save(user);
    }

    @Override
    public List<User> findAll() {
        return userMapper.findAll();
    }

    @Override
    public int updateById(User user) {
        return userMapper.updateById(user);
    }

    @Override
    public int deleteById(Long userId) {
        return userMapper.deleteById(userId);
    }
}

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

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

@Tag(name= "예제 APi", description = "Swagger 테스트용 API")
@RestController
@RequiredArgsConstructor
public class UserController {

    final UserService userService;
    final ResponseService responseService;

    @Operation(summary = "회원 단검 검색", description = "userId로 회원을 조회합니다.")
    @GetMapping("/user/{userId}")
    public SingleResult<User> findUserByKey(@Parameter(name = "userId",required = true) @PathVariable Long userId) {
        return responseService.getSingleResult(userService.findById(userId).orElse(null));
    }

    @Operation(summary = "회원 검색 (이름)" , description = "이름으로 회원을 검색합니다.")
    @GetMapping("/users/{name}")
    public ListResult<User> findUserByName(@Parameter(name = "name",required = true) @PathVariable String name) {
        return responseService.getListResult(userService.findByName(name));
    }

//    @Operation(summary = "회원 검색 (이메일)",description = "이메일로 회원 검색합니다.")
//    @GetMapping("/users/{email}")
//    public User findUserByEmail(@Parameter(name = "email",required = true) @PathVariable String email) {
//        return userService.findByEmail(email);
//    }

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

    @Operation(summary = "회원 저장" ,description = "이름 과 이메일을 받아 저장합니다.")
    @PostMapping("/user")
    public SingleResult<Integer> save(@Parameter(name = "email",required = true)
                        @RequestParam String email,
                    @Parameter(name = "name", required = true)
                        @RequestParam String name) {

        User user = User.builder()
                .email(email)
                .name(name)
                .build();

        return  responseService.getSingleResult(userService.save(user));
    }
    
    @Operation(summary = "회원 수정", description = "회원 정보를 수정합니다.")
    @PutMapping("/user")
    public CommonResult modify(
            @Parameter(name = "userId",required = true) @RequestParam Long userId,
            @Parameter(name = "email",required = true) @RequestParam String email,
            @Parameter(name = "name",required = true) @RequestParam String name
            ) {
        User user = User.builder()
                .userId(userId)
                .email(email)
                .name(name)
                .build();

        return responseService.getOperatorResult(userService.updateById(user));
    }

    @Operation(summary = "회원 삭제",description = "회원 정보를 수정합니다.")
    @DeleteMapping("/user/{userId}")
    public CommonResult delete(@Parameter(name = "userId",required = true)
                               @PathVariable Long userId) {

        userService.deleteById(userId);
        return responseService.getSuccessResult();

    }


}

기존의 블로그에서는 JPA를 써서 save 만하면 저장,수정이 다됬지만 mybatis 에는 그런 기능이 없어서 수정을 했다.