창작은 모방에서 나온다는 말이 있듯이 나는 개발을 배우는 과정에서 그 어떤 이론적 공부보다 그냥 예시 코드나 적용한 코드만 띡 던져주면 그게 그렇게 이해가 잘 된다.
코드도 마찬가지로 하나의 예술작품이다. 제 각각의 개발자들이 자신만의 화법을 이용해 코드를 그려나가고 그려진 아름다운 코드들은 마법같은 로직들로 연결되어 프로그램이 동작한다.
따라서 잘 그린 사람의 코드를 벤치마킹해서 내 것으로 만드는 2차 생산을 한다면. 그 사람의 코드도 내 것이 되고 내 것으로 만드는 과정에서 주물러 본 손맛들은 나를 발전시켜주고 새로운 지식을 일깨워준다.
그렇다. 요기요와 잘 만들어진 배민을 참고해서 음식 배달 서비스를 만들어 보고자 한다.
미리 그려둔 와이어프레임과 erd 데이터베이스 구조를 미리 짜놓고 보니 어느정도 밑그림이 그려진 것 마냥 금방 금방 해치워 나가서 서비스 구현에 대한 자신감이 고취된다.
하지만 현실은 그렇지 않았다 겨우 처음 짜본 데이터베이스로 어딜 가서 명함을 내밀려고 드는가?
어림없이 데이터베이스 벨리데이션 에러들에 무수하게 두들겨 맞게 된다.
그래서 현재 초보자인 나는 구조를 스케치하는 과정에서도, 백엔드를 구현하는 과정에서도, 프론트와 연결하는 과정에서도 DB수정은 그때 그때 필요게 되었다.
매우 귀찮았지만 결국 DB수정을 해 감으로써 데이터베이스(특히 관계형)에 대해서 더욱 이해하게 되고 점차 사랑에 빠지게 되고 있었다.
먼저 백엔드 파트에서 내가 맡게 된 부분은 사업자 등록 CRUD
, 배달 주문기능
, 주문 확인 기능
, 배달 완료 기능
이었다. 그리고 배달주문기능과, 배달완료 기능 중에세 포함되는 재화의 DB 과정은 하나의 로직에서라도 에러가 난다면 전체적인 흐름에 막대한 영향을 끼치기 때문에 구현하면서 중점적으로 생각하고 구현 할 Transaction
이다.
트랜젝션은 앞선 공부에서도 신나게 이론적으로 빠삭하게 배웠기 때문에 언제나 그렇듯 자신감은 항상 고취되어 있었다.
// 주문 결제 Transaction 적용
orderPayment = async (userId, MenuIds, restaurantId, orderDetails, totalPrice, orderPlace) => {
// 생성된 주문들을 담을 배열 초기화
const createdOrders = [];
// MenuIds에 있는 각 메뉴에 대해 주문 처리
for (const MenuId of MenuIds) {
const result = MenuId;
console.log("result", result);
// 트랜잭션 내에서 비동기 작업 수행
const [createdPoint, createdOrder] = await prisma.$transaction(async (tx) => {
// 주문하려는 메뉴가 존재하는지 확인
const checkedMenu = await tx.menu.findUnique({
where: {
menuId: result,
RestaurantId: +restaurantId,
},
});
if (!checkedMenu) throw new Error("불일치(메뉴,가게)");
// 사용자의 최신 포인트 정보 확인
const checkedPoint = await tx.point.findMany({
where: { UserId: userId },
orderBy: { createdAt: "desc" },
take: 1,
});
// 잔액 부족 시 에러 처리
if (checkedPoint[0].balance <= 0) throw new Error("잔액 부족");
// 사용자의 포인트 내역 업데이트
const createdPoint = await tx.point.create({
data: {
UserId: userId,
income: 0,
expense: totalPrice,
balance: checkedPoint[0].balance - totalPrice,
},
});
console.log(orderPlace);
// 주문 생성
const createdOrder = await tx.orders.create({
data: {
MenuId: result,
UserId: userId,
RestaurantId: +restaurantId,
orderDetails,
totalPrice,
orderPlace,
},
});
// 생성된 포인트와 주문을 배열에 추가
return [createdPoint, createdOrder];
});
createdOrders.push({ createdOrder, createdPoint });
}
// 모든 주문에 대한 결과 반환
return createdOrders;
};
에러 처리
: 더 자세한 에러 메시지를 통해서 디버깅 및 사용자 피드백을 개선할 수 있을 것 같다.
로깅
: 디버깅 및 에러 추적을 위해 더 의미있는 로그를 추가해야 한다.
트랜젝션 격리 수준
: 응용 프로그램의 요구에 따라서 적절한 격리수준이 필요하다.
사진으로 보면 로그인버튼 -> 주문 성공! 으로 간단한 로직 처럼 보이지만 저기 사이에는 몇 가지의 로직을 거쳐서 검증을 통과해야만 테이블에 컬럼을 추가할 수 있다.
결과적으로
userId
이 1인 사용자이다.
Point
테이블에 User
가 주문한 목록의 가격만큼 export가 입력되며 총잔액에서 마이너스 된다.
사장이 배달완료를 누르고 DB를 보면 userId
가 2인 사장의 계정으로는 정산된 돈이 들어오고
userId
을 가진 손님 또한 올바르게 정산되어 지불한 금액만큼 잔고에서 차감된다.
트랜잭션은 대표적으로 재화와 관련된 기능을 사용할 때 필수적으로 사용되는 기술로써 쿼리가 진행되다가 중간에 오류가 난다면 중간지점까지 commit이 되는게아니라 rollback을 시켜주는 바람에 데이터는 더욱 안전하게 보관될 수 있다.
마법같은 로직들~