

1. 📌 API란?
API에 관해서는 별개의 포스트에 기록해 놓았다.
https://jminie.tistory.com/120?category=1008953
API란? (SOAP API, REST API)
📌 API란? 우리가 레스토랑에 있다고 가정해보자. 우리는 점원이 가져다준 메뉴판을 보면서 음식을 고르면, 점원이 주문을 받아 요리사에 요청을 한다. 그러면 요리사는 음식을 만들어 점원에게
jminie.tistory.com
2. 📗 REST API와 HTTP 메서드
REST API는 HTTP 메서드를 사용한다.
메서드(method) : 동사 의미. - ex) GET, POST
URI: 행위의 목적 -ex) /users/userId/springboot
2.1. 메서드의 종류
- GET : 자료를 요청할 때 사용
- POST : 자료의 생성을 요청할 때 사용
- PUT : 자료의 전체의 수정을 요청할 때 사용
- PATCH : 자료의 일부의 수정을 요청할 때 사용
- DELETE : 자료의 삭제를 요청할 때 사용
2.2. GET과 POST의 차이
GET: Header만 필요 → 어떠한 값을 조회할 것인지에 대한 정보만 필요하기 때문에 Header만 필요하다.
POST: Header, Body 둘 다 필요 → 어디에 무엇을 저장할지에 대한 정보와 Body에 넣을 정보도 필요하다.
DELETE 기능은 가능한 사용하지 않는다.
최근 데이터는 기업의 귀중한 자산으로 사용된다. (머신러닝 등 다양한 방면에서 쓰임)
따라서 회원 탈퇴 등을 처리할 때 PATCH나 PUT 등을 통해 status 즉 상태를 변화시키는 방향으로 진행하는 것이 좋다.
2.3. URL의 2가지 형태
path variable: " : " 지정한다. → 특정한 값을 지목해서 처리 EX) userid=1, video=1의 데이터를 원할 때
query-string: " ? " 일종의 필터링. → 필터링을 활용한 처리 EX) 활성화 상태인 동영상 등을 원할 때
3. 데이터의 2가지 형태
XML: HTML 형태처럼 태그와 같은 형태로 데이터를 주고 받는다.
예제
<dog>
<name>토토</name>
<family>푸들<family>
<age>10</age>
<weight>4</weight>
</dog>
JSON : key, value 형태로 데이터를 주고받는다.
예제
{
"name": "토토",
"family": "푸들",
"age": 10,
"weight": 4
}
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에서 처리된다.
5. 📘 Validation 처리
서버 API 구성의 기본은 Validation을 잘 처리하는 것이다. 외부에서 어떤 값을 날리든 Validation을 잘 처리하여 서버가 다운되는 일이 없도록 유의해야 한다.
- 값, 형식, 길이 등의 형식적 Validation은 Controller에서 처리한다.
- DB에서 검증해야 하는 의미적 Validation은 Provider 혹은 Service에서 처리한다.
5.1. 형식적 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,"아이디를 입력해주세요."),
다음과 같은 내가 임의로 지정해둔 오류 코드와 메시지를 보여주게 된다.
5.2. 의미적 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까지 거슬러 올라가서 중복검사를 하게 되는 원리이다.
6. 📔 JDBC Template
6.1. JDBC란?
JDBC(Java Database Connectivity)는 자바 프로그램이 데이터베이스와 연결되어 데이터를 주고받을 수 있게 해주는 프로그래밍 인터페이스이다.
JDBC는 응용프로그램과 DBMS 간의 통신을 중간에서 번역해주는 역할을 한다.

6.2. 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"))
);
}
7. 📌 API 리스트업

우선 작성해야 할 API를 리스트업 한다.
8. 📌 API 기능 구현 및 작동 확인
(예시로 2개)
8.1. 회원 정보 전체 조회 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번 포트를 이용했다.

8.2. 회원가입 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 처리 확인

정상작동 확인

'YouTube Clone Project' 카테고리의 다른 글
YouTube 클론 프로젝트 - OAuth를 이용한 카카오 로그인 구현 (Spring Boot) (0) | 2021.10.29 |
---|---|
YouTube 클론 프로젝트 - JWT를 이용한 로그인 인증처리 (Spring Boot) (0) | 2021.10.18 |
YouTube 클론 프로젝트 - Product App(YouTube) 한 방 쿼리 작성 (0) | 2021.10.02 |
YouTube 클론 프로젝트 - Product App(YouTube) 데이터 모델링 및 ERD 구축 (0) | 2021.10.02 |