일주일 동안 기본적인 api 공부를 마치고 2주간 러쉬 홈페이지를 기반으로 한 프로젝트를 진행했다. 프로젝트가 끝난 기념으로 회고록을 작성하려고 한다.
개발 기간: 2022.09.19 ~ 2022.09.30
개발 내용: 러쉬 사이트를 클론한 디저트 판매 사이트
개발 상세 내용
프론트엔드 : 김대호(조장), 강루비, 양석문, 주혜린
백엔드 : 김정훈, 정다영, 정승렬
javascropt, node.js, express mysql8.0, Postman, JWT
러쉬 사이트를 배정받으며 가장 우려했던것은 저작권이었다. 이전에 러쉬측에서 이미지 사용을 허락받지 못한 사정이 있기에 우리 팀 또한 러쉬를 클론코딩 하되 디저트를 판매하는 사이트로 변경하였다.
trello를 활용해 프론트엔드와 일정을 공유하면서 체크리스트를 관리했다.
가장 먼저해야할 것은 db 모델링이었다. db는 프로젝트 중간에 수정하면 전체코드의 변경이 올 수 있는 참사가 일어나기에 팀원들과 가장 오랫동안 시간을 투자했다.
멘토님의 조언을 바탕으로 최종 완성된 ERD는 다음과 같다.
const createOrder = async (userId, productId, total, reqMessage, address) => {
await queryRunner.connect();
await queryRunner.startTransaction();
try{
userPoint = await orderDao.checkPoint(queryRunner,userId);
if(userPoint < total){
const error = new Error("LACK_OF_POINT")
error.statusCode = 400;
throw error;
}
await orderDao.deductPoint(queryRunner,userId, total);
const items = await orderDao.getCart(queryRunner, userId, productId);
const orderId = await orderDao.createOrder(queryRunner,userId, reqMessage, address);
await orderDao.createOrderItems(queryRunner,orderId, items);
const checkStock = await orderDao.checkStock(queryRunner,productId);
if(checkStock.length != 0 ){
const error = new Error(`${JSON.stringify(checkStock)}_IS_LACK`);
error.statusCode = 400;
throw error;
}
await orderDao.deleteProductStock(queryRunner,productId);
await orderDao.deleteCart(queryRunner, userId, productId);
await queryRunner.commitTransaction();
} catch (err) {
await queryRunner.rollbackTransaction();
const error = new Error('ROLLBACK' +' : ' + err.message);
error.statusCode = 400;
throw error
}
return;
}
트랜잭션이란 간단히 말해 데이터베이스의 상태를 변화시키기는 작업의 단위를 말한다. DB의 상태를 변화시킨다는 것은 sql를 이용하여 데이터베이스에 접근하는 것을 의미한다.
트랜잭션의 과정을 간단히 설정하자면 트랜잭션으로 묶은 단위는 한번에 모두 반영되어야 한다. 만약 한 곳에서 오류가 났다면 이미 처리된 트랜잭션을 다시 rollback
함으로서 원상복귀시킨다.
나는 queryrunner
을 활용해서 service층 내에 트랜잭션을 구현했다. service층에서 queryrunner
선언한 후 model층 안에 있는 메소드 안에 매개변수로 queryrunner
를 전달해준다. 그렇게 queryrunner
를 전달받은 모든 메소드들은 하나의 queryrunner
를 사용해게 되고 트랜잭션이 이루어진다.
query문이 존재하는 model층에 queryrunner
을 사용하지 않고 service층에서 선언한 이유는 무엇일까? 이유는 다음과 같다.
queryrunner
를 사용하기 위해서는 하나의 메소드안에 여러 query문을 선언해야 한다.그렇다면 그 query문은 재사용하기 어려워진다. 자바스크립트에서는 하나의 query를 사용하기 위해 여러개가 담긴 메소드를 호출해야 할 것이다.
메소드를 사용하고 싶지만 동시에 queryrunner
도 선언하고 싶다면 여러 queryrunner
를 선언해아 한다. 하지만 그렇게 되면 한 queryrunner
에서 동작하지 않으므로 트랜잭션이 이루어지기 어렵다.
const addCart = async (product_id, quantity, userId) => {
return await dataSource.query(`
INSERT INTO carts(
user_id,
product_id,
quantity
) VALUES (
?,
?,
?
) ON DUPLICATE KEY UPDATE
quantity = quantity + ?
`, [userId, product_id, quantity, quantity]
)
이번 코드는 다른 팀원의 코드를 리팩토링 하는 과정에서 작성했다. 카트에 제품이 1개 담겨있는 상황에서 내가 또 제품을 담으려면 update
문을 통해 제품의 quantity
만 변경해야 한다. 즉 데이터를 insert
할 때 이미 있는 데이터라면 지정된 컬럼 데이터를 update
하는 작업이다.
팀원은 카트에 있는 제품 리스트를 검사하는 메소드를 만들었다. 그래서 만약 리스트내에 제품이 있다면 update
없다면 insert
를 하도록 코드를 짰다. 하지만 리팩토링을 통해 upsert
를 통해 하나의 쿼리문으로 두 가지의 작업을 동시에 처리하도록 변경하였다.
const getProduct = async (productId) => {
const [result] = await dataSource.query(`
SELECT
p.id,
p.name,
p.price,
p.stock,
c.name as category_name,
p.thumbnail_image_url,
JSON_ARRAYAGG(i.image_url) AS image_url
FROM
products p
JOIN
categories c
ON
c.id = p.category_id
JOIN
product_images i
ON
p.id = i.product_id
WHERE
p.id = ?`, [productId]
)
if(typeof result.image_url == "string"){
result.image_url = result.image_url.replace("[",'');
result.image_url = result.image_url.replace("]",'');
result.image_url = result.image_url.replace(/"/g,'');
result.image_url = result.image_url.replace(/ /g,'');
result.image_url = result.image_url.split(",");
}
return result
마지막 코드 또한 다른 팀원의 코드를 리팩토링 하는 과정에서 작성했다.
JSON_ARRAYAGG
를 사용하면 group by
결과를 배열로 받을 수 있다.
예를 들어 한 제품이 2개의 이미지를 갖는다고 가정하자. JSON_ARRAYAGG
를 사용하지 않는다면 하나의 속성에서 이미지 url
로 구성된 2개의 데이터가 나오고, 나머지는 중복된 데이터들이 나온다. 우리는 중복된 데이터를 지우기 위해 JSON_ARRAYAGG
를 사용해 이미지라는 속성에 url
를 배열타입으로 묶어준다.
중간의 if
문은 mysql
의 오류를 처리하기 위한 코드이다. JSON_ARRAYAGG
로 묶은 데이터의 데이터타입은 mac OS에서 배열로 처리되지만 우분투는 string
으로 작성된다. 이는 mysql
의 오류이므로 이를 해결하기 위해 string
타입인 경우 배열로 처리하는 로직을 작성하게 되었다.
trello
, notion
, git
등을 활용하여 일정을 관리하고 코드를 공유하였다. 프로젝트를 위한 git
사용이 이번이 처음이 어려움이 컸다. 하지만 2주차를 지나서는 git
사용이 익숙해진 거 같아 개인적으로 가장 뿌듯하다. 다음에는 일정관리를 위한 툴을 좀 더 공부해보고 싶다. express
를 공부하며 미들웨어의 목적이나 활용이 이해가 안 갔다. 이번 프로젝트를 통해 미들웨어가 동작하는 원리를 알게되었고, 에러핸들링을 위한 미들웨어를 작성해보며 기본 자바스크립트의 실력 또한 향상된 거 같아 마음이 뿌듯했다.sql
문에 대해 공부를 많이 해야겠다고 다짐했다.github
에서 충돌이 많이 일어났다.DB
가 매우 간단했기에 api
추가 생성에는 어려움이 없었지만 기능추가는 독단적으로 진행할 수 없어 프론트와 조율이 필요했다. 하지만 시간 부족으로 결국 추가 기능 구현이 안되서 아쉬웠다.trello
를 통해 일정을 공유했지만 기한을 짧게 잡지 않았고, 또 기한내로 작업이 진행되지 않은 경우가 많았다. 아침에 무리하지 않게 오늘의 task를 정했다면 저녁까지는 완성을 목표로 하고, 저녁에 이를 체크하는 시간을 가졌으면 좀 더 수월한 진행이 이루어졌을 거 같다. 결국 1차프로젝트 때 아쉬웠던 점을 줄여보는 방향으로 2차 프로젝트를 진행해야 후회를 안 할 거 같다. 내가 2차 때 해야할 리스트를 작성해보겠다.
1. 초기 api 분기 작성
2. 간트 차트 작성
3. 하루 체크리스트 작성
4. 에러처리 고민
역시,, 다영님 프로젝트 말해뭐해 ~~~!! 😆👍