2주차
카카오 테크 캠퍼스 2단계 - BE - 2주차 클론 과제
과제명
1. 전체 API 주소 설계
2. Mock API Controller 구현
과제 설명
1. API주소를 설계하여 README에 내용을 작성하시오.
2. 가짜 데이터를 설계하여 응답하는 스프링부트 컨트롤러를 작성하고 소스코드를 업로드하시오.
과제 상세 : 수강생들이 과제를 진행할 때, 유념해야할 것
아래 항목은 반드시 포함하여 과제 수행해주세요!
전체 API 주소 설계가 RestAPI 맞게 설계되었는가? (예를 들어 배포된 서버는 POST와 GET으로만 구현되었는데, 학생들은 PUT과 DELETE도 배울 예정이라 이부분이 반영되었고, 주소가 RestAPI에 맞게 설계되었는지)
가짜 데이터를 설계하여 Mock API를 잘 구현하였는가? (예를 들어 DB연결없이 컨트롤러만 만들어서 배포된 서버의 응답과 동일한 형태로 데이터가 응답되는지 여부)
코드리뷰 관련: PR시, 아래 내용을 포함하여 코멘트 남겨주세요.
1. PR 제목과 내용을 아래와 같이 작성 해주세요.
PR 제목 : 부산대BE_라이언_2주차 과제
2. PR 내용 :
코드 작성하면서 어려웠던 점
코드 리뷰 시, 멘토님이 중점적으로 리뷰해줬으면 하는 부분
1. 전체 API 주소 설계
API주소를 설계하여 README에 내용을 작성하시오.
작성된 API를 Restful한 API로 변경하기
Restful한 API란?
- REST API의 설계 의도를 명확하게 지킨 API
- URI만 보더라도 리소스를 명확하게 인식할 수 있는 API
- 각 리소스 기능을 HTTP 메서드를 이용하여 일관되게 정의한 API
HTTP 메서드
GET : 조회
POST : 생성
PUT : 수정
DELETE : 삭제
상품(product)
1. 전체 상품 목록 조회
- GET
http://kakao-app-env.eba-kfsgeb74.ap-northeast-2.elasticbeanstalk.com/products
2. 개별 상품 상세 조회
- GET
http://kakao-app-env.eba-kfsgeb74.ap-northeast-2.elasticbeanstalk.com/products/:id
장바구니(cart)
1. 장바구니 조회 (전체 조회)
- GET
http://kakao-app-env.eba-kfsgeb74.ap-northeast-2.elasticbeanstalk.com/carts
- Request Header
Bearer
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJzc2FyQG5hdGUuY29tIiwicm9sZSI6Il
JPTEVfVVNFUiIsImlkIjoxLCJleHAiOjE2ODcwNTM5MzV9.fXlD0NZQXYYfPHV8rokRJTM86nhS869L
Z1KIGi7_qvPOcVbXgvyZLKvnlLxomIiS3YFnQRLzXAJ2G41yI_AmGg
2. 장바구니 담기 (저장)
- POST
http://kakao-app-env.eba-kfsgeb74.ap-northeast-2.elasticbeanstalk.com/carts
- Request Header
Bearer
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJzc2FyQG5hdGUuY29tIiwicm9sZSI6Il
JPTEVfVVNFUiIsImlkIjoxLCJleHAiOjE2ODcwNTM5MzV9.fXlD0NZQXYYfPHV8rokRJTM86nhS869L
Z1KIGi7_qvPOcVbXgvyZLKvnlLxomIiS3YFnQRLzXAJ2G41yI_AmGg
- Request Body
[ { "optionId":1, "quantity":5 }, { "optionId":2, "quantity":5 } ]
POST /carts/add ➡️ POST /carts
- URI에 행위에 대한 동사표현이 들어가면 안된다.
- 객체명은 복수명사를 사용한다.
3. 주문하기 (장바구니 수정)
- PUT
http://kakao-app-env.eba-kfsgeb74.ap-northeast-2.elasticbeanstalk.com/carts
- Request Header
Bearer
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJzc2FyQG5hdGUuY29tIiwicm9sZSI6Il
JPTEVfVVNFUiIsImlkIjoxLCJleHAiOjE2ODcwNTM5MzV9.fXlD0NZQXYYfPHV8rokRJTM86nhS869L
Z1KIGi7_qvPOcVbXgvyZLKvnlLxomIiS3YFnQRLzXAJ2G41yI_AmGg
- Request Body
[ { "cartId":4, "quantity":10 }, { "cartId":5, "quantity":10 } ]
POST /carts/update ➡️ PUT /carts
- 동사로 표현되는 행위들은 HTTP 메서드를 이용하여 정의한다.
주문(order)
1. 주문 결과 확인
- GET
http://kakao-app-env.eba-kfsgeb74.ap-northeast-2.elasticbeanstalk.com/orders/:id
- Request Header
Bearer
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJzc2FyQG5hdGUuY29tIiwicm9sZSI6Il
JPTEVfVVNFUiIsImlkIjoxLCJleHAiOjE2ODcwNTM5MzV9.fXlD0NZQXYYfPHV8rokRJTM86nhS869L
Z1KIGi7_qvPOcVbXgvyZLKvnlLxomIiS3YFnQRLzXAJ2G41yI_AmGg
2. 결제하기 (주문 저장)
- POST
http://kakao-app-env.eba-kfsgeb74.ap-northeast-2.elasticbeanstalk.com/orders
POST /orders/save ➡️ POST /orders
- URI에 행위에 대한 동사표현이 들어가면 안된다.
유저(user)
자원보다는 행위에 가까운 API들로 구성
1. 로그인
- POST
http://kakao-app-env.eba-kfsgeb74.ap-northeast-2.elasticbeanstalk.com/auth
- request body
{
"email":"ssar@nate.com",
"password":"meta1234!"
}
POST /login ➡️ POST /auth
- URI에 행위에 대한 동사표현이 들어가면 안된다.
2. 회원가입
- POST
http://kakao-app-env.eba-kfsgeb74.ap-northeast-2.elasticbeanstalk.com/users
- Request Body
{
"username":"meta",
"email":"meta@nate.com",
"password":"meta1234!"
}
POST /join ➡️ POST /users
- URI에 행위에 대한 동사표현이 들어가면 안된다.
3. 이메일 중복 확인
- POST
http://kakao-app-env.eba-kfsgeb74.ap-northeast-2.elasticbeanstalk.com/check-email
POST /check ➡️ POST /check-email
- 회원가입과 이메일 중복 확인 기능은 연관되어있으므로 함께 설계하려고 했으나,
restful api에서 / 으로 표현하는 계층관계는 리소스 포함관계를 뜻하므로 맞지 않아 분리하였다.- check뒤 email을 붙여 이메일에 대한 중복확인 의도를 명확하게 전달했다.
2. Mock API Controller 구현
가짜 데이터를 설계하여 응답하는 스프링부트 컨트롤러를 작성하고 소스코드를 업로드하시오.
Mock 이란?
모조품이란 뜻으로, 테스트 시 당장 구현하기 어려운 객체를 대신해 가짜로 생성해놓은 객체이다.
ApiUtils
API 응답처리를 위한 유틸리티 클래스이다.
성공 또는 실패에 따라 다른 응답을 생성한다.
package com.example.kakaoshop._core.utils;
import lombok.*;
import org.springframework.http.HttpStatus;
public class ApiUtils {
// 성공했을때
public static <T> ApiResult<T> success(T response) {
return new ApiResult<>(true, response, null);
}
// 실패했을때
public static ApiResult<?> error(String message, HttpStatus status) {
return new ApiResult<>(false, null, new ApiError(message, status.value()));
}
@Getter @Setter @AllArgsConstructor
public static class ApiResult<T> {
private final boolean success;
private final T response;
private final ApiError error;
}
//에러 정보
@Getter @Setter @AllArgsConstructor
public static class ApiError {
private final String message;
private final int status; //상태 코드
}
}
위 클래스를 통해 클라이언트에게 일관된 응답을 제공할 수 있다.
상품 목록 보기 Mock 구현
GET http://localhost:8080/products
응답 JSON
{
"success": true,
"response": [
{
"id": 1,
"productName": "기본에 슬라이딩 지퍼백 크리스마스/플라워에디션 에디션 외 주방용품 특가전",
"description": "",
"image": "/images/1.jpg",
"price": 1000
},
{
"id": 2,
"productName": "[황금약단밤 골드]2022년산 햇밤 칼집밤700g외/군밤용/생율",
"description": "",
"image": "/images/2.jpg",
"price": 2000
},
{
"id": 3,
"productName": "삼성전자 JBL JR310 외 어린이용/성인용 헤드셋 3종!",
"description": "",
"image": "/images/3.jpg",
"price": 30000
},
{
"id": 4,
"productName": "바른 누룽지맛 발효효소 2박스 역가수치보장 / 외 7종",
"description": "",
"image": "/images/4.jpg",
"price": 4000
},
{
"id": 5,
"productName": "[더주] 컷팅말랑장족, 숏다리 100g/300g 외 주전부리 모음 /중독성 최고/마른안주",
"description": "",
"image": "/images/5.jpg",
"price": 5000
},
{
"id": 6,
"productName": "굳지않는 앙금절편 1,050g 2팩 외 우리쌀떡 모음전",
"description": "",
"image": "/images/6.jpg",
"price": 15900
},
{
"id": 7,
"productName": "eoe 이너딜리티 30포, 오렌지맛 고 식이섬유 보충제",
"description": "",
"image": "/images/7.jpg",
"price": 26800
},
{
"id": 8,
"productName": "제나벨 PDRN 크림 2개. 피부보습/진정 케어",
"description": "",
"image": "/images/8.jpg",
"price": 25900
},
{
"id": 9,
"productName": "플레이스테이션 VR2 호라이즌 번들. 생생한 몰입감",
"description": "",
"image": "/images/9.jpg",
"price": 797000
}
],
"error": null
}
상품에 대한 정보를 보여준다.
응답 DTO
package com.example.kakaoshop.product.response;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class ProductRespFindAllDTO {
private int id;
private String productName;
private String description;
private String image;
private int price;
@Builder
public ProductRespFindAllDTO(int id, String productName, String description, String image, int price) {
this.id = id;
this.productName = productName;
this.description = description;
this.image = image;
this.price = price;
}
}
@Builder를 이용해 객체를 생성하고, @Getter, @Setter 어노테이션을 사용해 DTO를 구현하는 코드이다.
@Builder와 @Getter, @Setter
빌더 패턴과 getter, setter에 대해 정리한 글을 첨부한다.
https://shout-to-my-mae.tistory.com/305
✅ 우리 프로젝트에서는 Builder와 Setter 모두를 사용한다. 생성시 Builder를 사용하고, 수정시 Setter를 사용한다.
ProductRestController
@RestController
public class ProductRestController {
@GetMapping("/products")
public ResponseEntity<?> findAll() {
List<ProductRespFindAllDTO> responseDTO = new ArrayList<>();
// 상품 하나씩 집어넣기
ProductRespFindAllDTO dto1 = ProductRespFindAllDTO.builder()
.id(1)
.productName("기본에 슬라이딩 지퍼백 크리스마스/플라워에디션 에디션 외 주방용품 특가전")
.description("")
.image("/images/1.jpg")
.price(1000)
.build();
//담기
responseDTO.add(dto1);
// 상품 하나씩 집어넣기
ProductRespFindAllDTO dto2 = ProductRespFindAllDTO.builder()
.id(2)
.productName("[황금약단밤 골드]2022년산 햇밤 칼집밤700g외/군밤용/생율")
.description("")
.image("/images/2.jpg")
.price(2000)
.build();
//담기
responseDTO.add(dto2);
// 상품 하나씩 집어넣기
ProductRespFindAllDTO dto3 = ProductRespFindAllDTO.builder()
.id(3)
.productName("삼성전자 JBL JR310 외 어린이용/성인용 헤드셋 3종!")
.description("")
.image("/images/3.jpg")
.price(30000)
.build();
//담기
responseDTO.add(dto2);
// 상품 하나씩 집어넣기
ProductRespFindAllDTO dto4 = ProductRespFindAllDTO.builder()
.id(4)
.productName("바른 누룽지맛 발효효소 2박스 역가수치보장 / 외 7종")
.description("")
.image("/images/4.jpg")
.price(4000)
.build();
//담기
responseDTO.add(dto4);
// 상품 하나씩 집어넣기
ProductRespFindAllDTO dto5 = ProductRespFindAllDTO.builder()
.id(5)
.productName("[더주] 컷팅말랑장족, 숏다리 100g/300g 외 주전부리 모음 /중독성 최고/마른안주")
.description("")
.image("/images/5.jpg")
.price(5000)
.build();
//담기
responseDTO.add(dto5);
// 상품 하나씩 집어넣기
ProductRespFindAllDTO dto6 = ProductRespFindAllDTO.builder()
.id(6)
.productName("굳지않는 앙금절편 1,050g 2팩 외 우리쌀떡 모음전")
.description("")
.image("/images/6.jpg")
.price(15900)
.build();
//담기
responseDTO.add(dto6);
// 상품 하나씩 집어넣기
ProductRespFindAllDTO dto7 = ProductRespFindAllDTO.builder()
.id(7)
.productName("eoe 이너딜리티 30포, 오렌지맛 고 식이섬유 보충제")
.description("")
.image("/images/7.jpg")
.price(26800)
.build();
//담기
responseDTO.add(dto7);
// 상품 하나씩 집어넣기
ProductRespFindAllDTO dto8 = ProductRespFindAllDTO.builder()
.id(8)
.productName("제나벨 PDRN 크림 2개. 피부보습/진정 케어")
.description("")
.image("/images/8.jpg")
.price(25900)
.build();
//담기
responseDTO.add(dto8);
// 상품 하나씩 집어넣기
ProductRespFindAllDTO dto9 = ProductRespFindAllDTO.builder()
.id(9)
.productName("플레이스테이션 VR2 호라이즌 번들. 생생한 몰입감")
.description("")
.image("/images/9.jpg")
.price(797000)
.build();
//담기
responseDTO.add(dto9);
return ResponseEntity.ok().body(ApiUtils.success(responseDTO));
}
}
Mock을 만들어 응답 데이터로 전송한다.
@RestController와 ResponseEntity
따로 정리한 글
https://shout-to-my-mae.tistory.com/306
Mock 테스트 수행
@AutoConfigureMockMvc
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
public class ProductRestControllerTest {
@Autowired
private MockMvc mvc;
@Test
@DisplayName("전체 상품 목록 조회")
public void findAll_test() throws Exception {
// when
ResultActions resultActions = mvc.perform(
get("/products")
);
String responseBody = resultActions.andReturn().getResponse().getContentAsString();
System.out.println("테스트 : " + responseBody);
// verify
resultActions.andExpect(jsonPath("$.success").value("true"));
resultActions.andExpect(jsonPath("$.response[0].id").value(1));
resultActions.andExpect(jsonPath("$.response[0].productName").value("기본에 슬라이딩 지퍼백 크리스마스/플라워에디션 에디션 외 주방용품 특가전"));
resultActions.andExpect(jsonPath("$.response[0].description").value(""));
resultActions.andExpect(jsonPath("$.response[0].image").value("/images/1.jpg"));
resultActions.andExpect(jsonPath("$.response[0].price").value(1000));
resultActions.andExpect(jsonPath("$.response[1].id").value(2));
resultActions.andExpect(jsonPath("$.response[1].productName").value("[황금약단밤 골드]2022년산 햇밤 칼집밤700g외/군밤용/생율"));
resultActions.andExpect(jsonPath("$.response[1].description").value(""));
resultActions.andExpect(jsonPath("$.response[1].image").value("/images/2.jpg"));
resultActions.andExpect(jsonPath("$.response[1].price").value(2000));
}
}
응답이 mock과 일치하는지 테스트한다.
@AutoConfigureMockMvc, MockMvc, JsonPath
정리한 글
https://shout-to-my-mae.tistory.com/307
Mock 테스트 코드 일부
String responseBody = resultActions.andReturn().getResponse().getContentAsString();
System.out.println("테스트 : " + responseBody);
응답 결과를 문자열로 출력하는 코드이다.
resultActions.andExpect(jsonPath("$.success").value("true"));
resultActions.andExpect(jsonPath("$.response[0].id").value(1));
jsonPath가 반환한 JsonPathResultMatchers에서 제공하는 value함수를 이용해 테스트 값을 비교한다.
'Spring > 카테캠 - TIL' 카테고리의 다른 글
TIL[0706] : 2주차 과제 - Mock Controller 3(마무리) (0) | 2023.07.07 |
---|---|
TIL[0706] : 2주차 과제 - Mock Controller 2 (0) | 2023.07.06 |
카테캠 : 1주차 코드리뷰 (0) | 2023.07.04 |
TIL [0704] - 2주차 강의 2 : Spring Security (0) | 2023.07.04 |
TIL[0703] : 2주차 강의 1 (0) | 2023.07.03 |