728x90

 

 

📌  API란?

API에 관해서는 별개의 포스트에 기록해 놓았다.

https://jminie.tistory.com/120?category=1008953 

 

API란? (SOAP API, REST API)

📌 API란? 우리가 레스토랑에 있다고 가정해보자. 우리는 점원이 가져다준 메뉴판을 보면서 음식을 고르면, 점원이 주문을 받아 요리사에 요청을 한다. 그러면 요리사는 음식을 만들어 점원에게

jminie.tistory.com

 

 

 

 

 

 

📗  REST API와 HTTP 메서드

REST API는 HTTP 메서드를 사용한다.

 

메서드(method) : 동사 의미. - ex) GET, POST

URI: 행위의 목적 -ex) /users/userId/springboot

 

메서드의 종류

  • GET : 자료를 요청할 때 사용
  • POST : 자료의 생성을 요청할 때 사용
  • PUT : 자료의 전체의 수정을 요청할 때 사용
  • PATCH : 자료의 일부의 수정을 요청할 때 사용
  • DELETE : 자료의 삭제를 요청할 때 사용

 

 

 

GET과 POST의 차이

GET: Header만 필요 → 어떠한 값을 조회할 것인지에 대한 정보만 필요하기 때문에 Header만 필요하다.

POST: Header, Body 둘 다 필요 → 어디에 무엇을 저장할지에 대한 정보와 Body에 넣을 정보도 필요하다.

 

DELETE 기능은 가능한 사용하지 않는다.

최근 데이터는 기업의 귀중한 자산으로 사용된다. (머신러닝 등 다양한 방면에서 쓰임)

따라서 회원 탈퇴 등을 처리할 때 PATCH나 PUT 등을 통해 status 즉 상태를 변화시키는 방향으로 진행하는 것이 좋다.

 

 

URL의 2가지 형태

path variable: " : " 지정한다. → 특정한 값을 지목해서 처리 EX) userid=1, video=1의 데이터를 원할 때

query-string: " ? " 일종의 필터링. → 필터링을 활용한 처리 EX) 활성화 상태인 동영상 등을 원할 때

  

 

데이터의 2가지 형태

 

XML: HTML 형태처럼 태그와 같은 형태로 데이터를 주고 받는다.

 

예제

<dog>
    <name>토토</name>
    <family>푸들<family>
    <age>10</age>
    <weight>4</weight>
</dog>

 

 

 

JSON : key, value 형태로 데이터를 주고받는다.

 

예제

{
    "name": "토토",
    "family": "푸들",
    "age": 10,
    "weight": 4
}

 

 

 

📙  도메인 폴더 구조

Route - Controller - Provider/Service - DAO

  • Route: Request에서 보낸 라우팅 처리해준다.
  • Controller: Request를 처리하고 Response 해주는 곳이다. (Provider/Service에 넘겨주고 다시 받아온 결과값을 형식화), 형식적 Validation
  • Provider/Service: 비즈니스 로직 처리한다. 의미적 Validation
  • DAO: Data Access Object의 줄임말. Query가 작성되어 있는 곳이다.

 

 

위 그림에서는 Service 안에 Provider가 포함되어 있다고 생각하면 된다.

필자는 

  • Provider : Read의 비즈니스 로직 처리
  • Service : Create, Update, Delete 의 로직 처리

방식으로 처리했다.

 

Spring Boot는 Route와 Controller가 모두 Controller에서 처리된다.

 

 

 

 

📘  Validation 처리

서버 API 구성의 기본은 Validation을  처리하는 것이다. 외부에서 어떤 값을 날리든 Validation을  처리하여 서버가 다운되는 일이 없도록 유의해야 한다.

  •  값, 형식, 길이 등의 형식적 Validation은 Controller에서 처리한다.
  •  DB에서 검증해야 하는 의미적 Validation은 Provider 혹은 Service에서 처리한다.

 

 

 

 

형식적 Validation 예시

User Controller

    /**
     * 회원가입 API
     * [POST] /users
     * @return BaseResponse<PostUserRes>
     */
    @ResponseBody
    @PostMapping("")
    public BaseResponse<TestPostUserRes> createUser(@RequestBody TestPostUserReq testpostUserReq) {
        //닉네임 입력을 안했을 때
        if(testpostUserReq.getUserNickname() == null){
            return new BaseResponse<>(POST_USERS_EMPTY_NICKNAME);
        }
        //닉네임 정규표현
        if(!isRegexNickname(testpostUserReq.getUserNickname())){
            return new BaseResponse<>(POST_USERS_INVALID_NICKNAME);
        }
        //이메일 입력을 안했을 때
        if(testpostUserReq.getUserEmail() == null){
            return new BaseResponse<>(POST_USERS_EMPTY_EMAIL);
        }
        //이메일 정규표현
        if(!isRegexEmail(testpostUserReq.getUserEmail())){
            return new BaseResponse<>(POST_USERS_INVALID_EMAIL);
        }
        try{
            TestPostUserRes postUserRes = userService.createUser(testpostUserReq);
            return new BaseResponse<>(postUserRes);
        } catch(BaseException exception){
            return new BaseResponse<>((exception.getStatus()));
        }
    }

 

형식적 Validation은 다음과 같이 Controller에서 처리한다. 위 코드를 예시로 들면 닉네임의 형식과 이메일의 형식 등의 Validation을

if문을 User Controller에서 처리하고 있다. 만약 닉네임을 입력하지 않았다면 첫 번째 if문에서 걸려 POST_USERS_EMPTY_NICKNAME에 걸려 해당 BaseResponse로 빠져

 

    // [POST] /users
    POST_USERS_EMPTY_NICKNAME(false,2012,"아이디를 입력해주세요."),

 

다음과 같은 내가 임의로 지정해둔 오류 코드와 메시지를 보여주게 된다.

 

 

 

 

 

 

 

의미적 Validation 예시

User Service

    //POST
    public TestPostUserRes createUser(TestPostUserReq testpostUserReq) throws BaseException {
        //닉네임 중복
        if(userProvider.checkNickname(testpostUserReq.getUserNickname()) ==1){
            throw new BaseException(POST_USERS_EXISTS_NICKNAME);
        }

        //이메일 중복
        if(userProvider.checkEmail(testpostUserReq.getUserEmail()) ==1){
            throw new BaseException(POST_USERS_EXISTS_EMAIL);
        }

 

의미적 Validation은 다음과 같이 Service에서 처리한다. 위 코드와 같이 형식은 맞았지만 DB에 중복되는 데이터가 있는 경우 Service단에서 중복을 확인하고 POST_USERS_EXISTS_NICKNAME에 걸리게 된다.

작동원리를 간략히 살펴보면 userProvider.checkNickname을 통해 Provider로 넘어가 checkNickname메서드를 확인하게 된다.

 

User Provider(checkNickname메서드)

 

    public int checkNickname(String nickName) throws BaseException{
        try{
            return userDao.checkNickname(nickName);
        } catch (Exception exception){
            throw new BaseException(DATABASE_ERROR);
        }
    }

 

여기서는 또 DAO의 checkNickname메서드로 안내해준다. (DAO로 연결될 수 없다면 DB 연결 오류인 DATABASE_ERROR를 띄워준다.)

 

User DAO(checkNickname메서드)

    public int checkEmail(String userEmail){
        String checkEmailQuery = "select exists(select userEmail from User where userEmail = ?)";
        String checkEmailParams = userEmail;
        return this.jdbcTemplate.queryForObject(checkEmailQuery,
                int.class,
                checkEmailParams);
    }

 

여기서 checkEmailQuery문을 통해 만약 DB에 같은 데이터(여기서는 이메일)가 존재한다면 1을 반환하게 된다. 이렇게 다시 Srivice까지 거슬러 올라가서 중복검사를 하게 되는 원리이다.

 

 

 

 

 

 

 

📔  JDBC Template

JDBC란?

JDBC(Java Database Connectivity)는 자바 프로그램이 데이터베이스와 연결되어 데이터를 주고받을 수 있게 해주는 프로그래밍 인터페이스이다.

 

JDBC는 응용프로그램과 DBMS 간의 통신을 중간에서 번역해주는 역할을 한다.

 

 

 

JDBC 설정

jdbcTemplate을 사용하기 위해 다음과 같이 dataSource를 주입해준다.

// template 정의
private JbdcTemplate jdbcTemplate;

// DataSource 전달
public  void setDataSource(DataSource dataSource) {
    this.jdbcTemplate = new JbdcTemplate(dataSource);
}

 

내가 구성한 API에서 JDBC는 DAO에서 크게 두 가지 메서드로 쓰이는데 

 

 

1. queryForObject() 메서드 -> 단일 반환 값을 받을 때 사용

(Ex : userId가 특정 값인 단일 반환 값을 불러와라)

    public GetUserRes getUser(int userId){
        String getUserQuery = "select * from User where userId =?";
        int getUserParams = userId;
        return this.jdbcTemplate.queryForObject(getUserQuery,
                (rs, rowNum) -> new GetUserRes(
                        rs.getInt("userId"),
                        rs.getString("userNickname"),
                        rs.getString("userEmail"),
                        rs.getString("ID"),
                        rs.getString("userPassword")),
                getUserParams);
    }

 

 

2. query() 메서드 -> 여러 개의 반환 값을 받을 때 사용

(Ex : 전체 유저 목록을 여러 반환 값으로 가져와라)

    public List<GetUserRes> getUsers(){
        String getUsersQuery = "select * from User";
        return this.jdbcTemplate.query(getUsersQuery,
                (rs,rowNum) -> new GetUserRes(
                        rs.getInt("userId"),
                        rs.getString("userNickname"),
                        rs.getString("userEmail"),
                        rs.getString("ID"),
                        rs.getString("userPassword"))
                );
    }

 

 

 

 

 

📌  API 리스트업

 

 

우선 작성해야 할 API를 리스트업 한다. 

 

 

 

 

 

📌  API 기능 구현 및 작동 확인

 

(예시로 2개)

회원 정보 전체 조회 API

-> GET메서드를 사용하므로 Service 단 에서는 따로 구현할 필요가 없었다.

 

 

UserController

    /**
     * 회원 전체 정보 조회 API
     * [GET] /users
     * 회원 번호 및 이메일 검색 조회 API
     * [GET] /users? Email=
     */
    //Query String
    @ResponseBody
    @GetMapping("") // (GET) 127.0.0.1:9000/app/users
    public BaseResponse<List<GetUserRes>> getUsers(){
        try{
            List<GetUserRes> getUsersRes = userProvider.getUsers();
            return new BaseResponse<>(getUsersRes);
        } catch(BaseException exception){
            return new BaseResponse<>((exception.getStatus()));
        }
    }

 

 

 

 

UserProvider

    public List<GetUserRes> getUsers() throws BaseException{
        try{
            List<GetUserRes> getUserRes = userDao.getUsers();
            return getUserRes;
        }
        catch (Exception exception) {
            throw new BaseException(DATABASE_ERROR);
        }
    }

 

 

 

 

UserDao

    public List<GetUserRes> getUsers(){
        String getUsersQuery = "select * from User";
        return this.jdbcTemplate.query(getUsersQuery,
                (rs,rowNum) -> new GetUserRes(
                        rs.getInt("userId"),
                        rs.getString("userNickname"),
                        rs.getString("userEmail"),
                        rs.getString("ID"),
                        rs.getString("userPassword"))
                );
    }

 

 

 

 

작동 확인 (Postman)

필자는 로컬에서 9000번 포트를 이용했다.

 

 

 

 

 

 

 

회원가입 API

 

UserController

    /**
     * 회원가입 API
     * [POST] /users
     * @return BaseResponse<PostUserRes>
     */
    @ResponseBody
    @PostMapping("")
    public BaseResponse<TestPostUserRes> createUser(@RequestBody TestPostUserReq testpostUserReq) {
        //이메일 입력을 안했을 때
        if(testpostUserReq.getUserEmail() == null){
            return new BaseResponse<>(POST_USERS_EMPTY_EMAIL);
        }
        //이메일 정규표현
        if(!isRegexEmail(testpostUserReq.getUserEmail())){
            return new BaseResponse<>(POST_USERS_INVALID_EMAIL);
        }
        //닉네임 입력을 안했을 때
        if(testpostUserReq.getUserNickname() == null){
            return new BaseResponse<>(POST_USERS_EMPTY_NICKNAME);
        }
        //닉네임 정규표현
        if(!isRegexNickname(testpostUserReq.getUserNickname())){
            return new BaseResponse<>(POST_USERS_INVALID_NICKNAME);
        }

        try{
            TestPostUserRes postUserRes = userService.createUser(testpostUserReq);
            return new BaseResponse<>(postUserRes);
        } catch(BaseException exception){
            return new BaseResponse<>((exception.getStatus()));
        }
    }

 

 

 

 

UserService

    //POST
    public TestPostUserRes createUser(TestPostUserReq testpostUserReq) throws BaseException {
        //닉네임 중복
        if(userProvider.checkNickname(testpostUserReq.getUserNickname()) ==1){
            throw new BaseException(POST_USERS_EXISTS_NICKNAME);
        }

        //이메일 중복
        if(userProvider.checkEmail(testpostUserReq.getUserEmail()) ==1){
            throw new BaseException(POST_USERS_EXISTS_EMAIL);
        }

        String pwd;
        try{
            //암호화
            pwd = new AES128(Secret.USER_INFO_PASSWORD_KEY).encrypt(testpostUserReq.getUserPassword());
            testpostUserReq.setUserPassword(pwd);
        } catch (Exception ignored) {
            throw new BaseException(PASSWORD_ENCRYPTION_ERROR);
        }

        try{
            int userId = userDao.createUser(testpostUserReq);
           // jwt 발급.
//            String jwt = jwtService.createJwt(userId);
            return new TestPostUserRes(userId);
        } catch (Exception exception) {
            throw new BaseException(DATABASE_ERROR);
        }
    }

암호화와 jwt에 대해서는 추후 포스팅을 통해 다루도록 하겠다.

 

 

 

 

UserDao

    public int createUser(TestPostUserReq testpostUserReq){
        String createUserQuery = "insert into User (userNickname, userEmail, userPassword, status,ID ) VALUES (?,?,?,?,?)";
        Object[] createUserParams = new Object[]{testpostUserReq.getUserNickname(), testpostUserReq.getUserEmail(), testpostUserReq.getUserPassword(), testpostUserReq.getStatus(), testpostUserReq.getID()};
        this.jdbcTemplate.update(createUserQuery, createUserParams);
        String lastInserIdQuery = "select last_insert_id()";
        return this.jdbcTemplate.queryForObject(lastInserIdQuery, int.class);
    }

 

 

 

 

작동 확인(Postman)

 

형식적 Validation 처리 확인

 

 

 

의미적 Validation 처리 확인

 

 

 

 

정상작동 확인

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

복사했습니다!