sprint-cmarket-database 개발과정

flobeeee·2021년 2월 27일
4

Sprint

목록 보기
18/25

🛒 시작하기

이번에는 데이터베이스를 통해, 데이터를 주고받는 것을 배운다.

  • 주의: 이번 스프린트는 npm test하면 자동으로 서버가 실행되고 테스트가 진행된다. 그래서 켜둔 서버를 끄고 테스트를 돌려야한다.
  1. mysql에 접속하여 cmarket 데이터베이스를 생성한다.

  2. 미리 구성되어 있는 Cmarket 스키마를 기반으로 MySQL에 cmarket 데이터베이스의 테이블을 생성한다.

  • $ mysql -u root -p < server/schema.sql -Dcmarket 명령어를 이용하여 cmarket 데이터베이스에 테이블을 생성.
  • $ mysql -u root -p < server/seed.sql -Dcmarket 명령어를 이용하여 생성한 테이블에 기반이 되는 데이터를 저장.

명령어에 명시된 파일을 들어가보면 schema.sql에는 스키마가 미리 작성되어 있다.
seed.sql에는 데이터를 추가하는 명령어가 작성되어 있다.
-> 미리 파일을 만들어두고 명령어를 실행하는 방식으로 데이터베이스에 테이블을 생성하고 데이터를 추가할 수 있는 것 같다.

스프린트를 진행하면서 잘못된 sql로 작성해서 이상한 데이터들이 쌓여있었다.
DROP DATABASE cmarket; 를 통해 데이터베이스를 전부 삭제하고,
위 1번부터 2번까지 과정을 다시 반복하였다.

🛒 파일 파악하기 (server 폴더)

  1. package.json을 확인하면 dependenciesmysql이 있다.
    이 모듈은 서버와 데이터베이스서버를 연결해주는 역할을 하는 것 같다.

  2. config/config.js파일은 .env 와 연결되어 있다. 보통 .env 파일을 통해 비밀번호를 설정하고, config.js 파일에서 기본값을 고정 시켜주는 것 같다.

  3. app.js 파일에는 express로 서버를 만드는 코드가 이미 작성되어있다.
    (express.js는 node.js환경에서 웹어플리케이션 혹은 API를 제작하기 위해 사용되는 인기있는 프레임워크)

  4. controllers/index.js get, post 요청을 처리하는 코드가 이미 작성되어 있다. API문서를 참고하여 controller가 어떻게 서버에 요청을 보내고 응답을 전송하는지 알 수 있다.

  5. db/index.js 파일에서는 mysql 모듈을 사용해 데이터베이스와 서버를 연결한다.

  6. models/index.js 파일은 controller에서 사용할 orders, items 모델을 정의해야 한다. 기본적인 틀은 짜여져 있고,db/index.js의 함수를 불러와 SQL을 사용하여 쿼리문을 통해 DB의 정보를 처리한다. 데이터베이스 쿼리는 반드시 비동기 요청인점을 고려해야 한다.

🛒 진행하기

테스트 항목은 3가지 였다.

  • 해당 유저가 작성한 모든 주문을 가져오는 함수를 작성
  • 해당 유저의 주문 요청을 데이터베이스에 생성하는 함수를 작성
  • Cmarket의 모든 상품을 가져오는 함수를 작성

Cmarket의 모든 상품을 가져오는 함수를 작성

마지막 항목을 제일 먼저 구현하기로 했다. 데이터베이스를 확인했을 때, 모든 상품이 있는 테이블에만 데이터가 존재했기 때문이다.

items: {
    get: (callback) => {
      // TODO: Cmarket의 모든 상품을 가져오는 함수를 작성하세요
      callback(err, result);
    }
  }
  1. 유어클래스에 있는 예제를 이용하여 작성했다.
var sql = "INSERT INTO customers (name, address) VALUES ?";
var params = [
  ["John", "Highway 71"],
  ["Peter", "Lowstreet 4"],
  ["Amy", "Apple st 652"],
  ["Hannah", "Mountain 21"],
  ["Michael", "Valley 345"],
  ["Sandy", "Ocean blvd 2"],
  ["Betty", "Green Grass 1"],
  ["Richard", "Sky st 331"],
  ["Susan", "One way 98"],
  ["Vicky", "Yellow Garden 2"],
  ["Ben", "Park Lane 38"],
];
con.query(sql, [params], function (err, result) {
  if (err) throw err;
  console.log("Number of records inserted: " + result.affectedRows);
});
  1. sql 에 쿼리문을 작성하고, 아래 con.query를 이용하면 되는 것 같았다.
    그대로 가져와서 모든 상품을 가져오는 쿼리문을 작성하고 테스트를 돌렸다.
    con이 정의되지 않았다는 에러가 떴다.
    파일의 맨위에 const db = require('../db'); 이 코드가 작성되어 있었다.해당 파일에 들어가보니 con 이라는 변수가 작성되어 있었고 module.exports = con;를 통해 내보내고 있었다.
  1. db.query ~~로 작성하고 기존에 작성되어 있던 callback(err, result); 코드를 if (err) throw err; 대신 넣어주었다.

  2. 클라이언트를 실행하여 확인하니 모든 상품이 잘 보였다!

해당 유저의 주문 요청을 데이터베이스에 생성하는 함수를 작성

그 다음 두번째 항목을 먼저 구현하기로 했다.
첫번째 항목은 유저의 주문목록을 가져오는 것인데, 이것은 데이터베이스에서 가져와야 하기 때문이다. 그래서 데이터베이스에 주문 요청을 저장하는 항목이 먼저 구현되어야 한다고 생각했다.

orders: {
    get: (userId, callback) => {
       // TODO: 해당 유저가 작성한 모든 주문을 가져오는 함수를 작성하세요
       callback(err, result);
    },
    post: (userId, orders, totalPrice, callback) => {
      // TODO: 해당 유저의 주문 요청을 데이터베이스에 생성하는 함수를 작성하세요
     callback(err, result);
    }
  }
  1. 파라미터 파악
    post: (userId, orders, totalPrice, callback) 를 보면,
    유저아이디, 주문들, 총금액을 받아온다. 데이터베이스에 각각 추가해줘야 하는 내용이다.
    그리고 테스트 케이스를 확인해보니 아래처럼 작성되어 있었다.
    즉, orders.itemId, orders.quantity 를 통해 쿼리를 날려야 한다.
orders: [ { itemId: 1, quantity: 2 },
            { itemId: 2, quantity: 5 }]
  1. 쿼리 두번 날리기
    데이터베이스를 확인해보면 아래처럼 구성되어 있다.

먼저 orders 테이블에 userId, totalPrice를 추가해줘야 하고, orders의 id(PK)를 받아온다.
order_items 테이블에 orders.itemId와 orders.quantity를 추가해줘야 한다. 더불어 orders 테이블의 id(PK)를 order_items의 order_id(FK)에 넣어줘야한다.
그래야 연결이 되기 때문이다.

let sql = `INSERT INTO ~~~`;
// 1번째 쿼리문
db.query(sql, (err, result) => {
  if (result) { // result 받아와서 있으면 다음 쿼리문 실행
    let sql = `INSERT INTO ~~ VALUES ?;`; // ? 를 사용하면
    let params= []; // param 문이 자동으로 들어간다.
    
    // 2번째 쿼리문
    db.query(sql, [params], (err, result) => { // [params] 를 넣어줘야한다.
       callback(err, result);
    })
  }
})
  1. PK 받아오기
    INSERT INTO 가 받아오는 객체를 확인하면 PK를 받아오는 방법을 알 수 있다.
    result.insertId 를 이용하면 된다.
    if (result) {console.log(result)} 를 찍어보면 아래 내용을 확인할 수 있다.
OkPacket {
  fieldCount: 0,
  affectedRows: 1,
  insertId: 1,
  serverStatus: 2,
  warningCount: 0,
  message: '',
  protocol41: true,
  changedRows: 0
}
  1. 데이터베이스에 잘 저장하는지 확인해보자

    위 화면처럼 아이템을 담아서 구매하기 버튼을 누른다.

    잘 추가되어있다!

해당 유저가 작성한 모든 주문을 가져오는 함수를 작성

  1. 테스트케이스를 보고 어떤 데이터를 가져와야 하는지 확인한다.
          expect(data[0].id).to.equal(1);
          expect(data[0].image).to.equal('../images/egg.png');
          expect(data[0].name).to.equal('노른자 분리기');
          expect(data[0].order_quantity).to.equal(2);
          expect(data[0].price).to.equal(9900);
          expect(data[0].total_price).to.equal(79800);
          
          expect(data[3].id).to.equal(2);
          expect(data[3].image).to.equal('../images/fish.jpg');
          expect(data[3].name).to.equal('잉어 슈즈');
          expect(data[3].order_quantity).to.equal(2);
          expect(data[3].price).to.equal(3900);
          expect(data[3].total_price).to.equal(10700);

이 6가지의 데이터를 가져오면 테스트는 통과하지만,
클라이언트를 확인해보면 주문내역 탭을 눌렀을 때, 에러가 뜬다.
추가로 orders.created_at 데이터까지 가져오면 아래처럼 잘 작동한다.

🛒 후기

드디어 3티어 아키텍처를 경험해봤다.
부분적으로 작성하면 가능하지만, 밑바닥부터 만들기 시작하면 내가 해낼 수 있을까?
그래도 다 만들고 난 후에는 너무 뿌듯했다.

test 스크립트는 시간제한이 2000 이었지만, 자꾸 타임아웃 에러가 떠서 시간을 더 늘려야했다. 나중에는 쿼리문을 엄청 작성해놓고 타임아웃이 뜨자 파일에 대해 의심했는데, 스프린트를 다 완성한 지금 와서는 내가 쿼리를 개판으로 날렸기 때문에 일어난 일이었다 ^^;

어렵지만 너무 재미있다. 나중에는 나 혼자서 클라이언트, 서버, 데이터베이스까지 만들어보고싶다! 우선은 여태 배운거 다 복습하기!

스프린트 진행했던 파일들 차곡차곡 잘 모아두면 나중에 좋은 참고서가 될 것이라고 생각한다.

profile
기록하는 백엔드 개발자

0개의 댓글