09/05, study on saturday

Ian·2020년 9월 5일
0

Today I Learned

목록 보기
18/40

오늘 해결하고 싶은 것들

JEST 로 만든 Test Case 를 수정하고, 통과하기

어제 만든 chatterbox-database 는 일단 GET, POST 요청은 잘 받아지고, sequel 등에서 DB query 를 발송해봐도 잘 적용된다. 그러나, JEST 로 만든 Test Case로부터 발송되는 요청과 DB query 는 일부가 적용되지 않아 통과하지 못 하고 있다. 그래서 그 문제를 오늘 마저 해결해 보기 위해 고군분투 중이다!

무엇이 문제점일까? - 분석하기

Test case 의 request 모듈을 통한 POST request 는 서버에서 잘 받아주고 있다. 200번대 status code 가 나오고, 그리고 내가 설정한 대로 "POST request를 보내면, request body 에 담아 보낸 JSON 을 담아 response 하라" 라는 대로 test case 에서 body 에 잡아준 JSON 도 잘 나온다. 그리고 그게 DB query 로 넘어가는 것도 맞는데,

확인해 본 결과, 정황상 메시지는 잘 가는데, truncate 을 통해 매 test 가 시작하기 전 messages table 을 초기화 하기에 안 보이는 것을 확인하였다. 즉 쿼리는 잘 받아주고, 잘 전송하고 있다.

분석 완료, mysql module 의 result scope 문제

확인해 봤는데, test case 는 callback 을 걸어주지 않고 바로 넘겨서 result 에 접근할 수 없던 것 같다. 일단 추측은 그러하다. 그래서 일단은 수정 전 test case 를 통해 다시 실험해보았다.


Reference Code 참조

이번에는 도저히 이해가 되지 않아 reference code 를 참조하였다. Schema Design 은 나와 거의 똑같았다. 그런데, 서버에서 들어오는 응답을 처리해주는 부분과, 그리고 그 응답과 같이 SQL을 연동하는 부분이 달랐다. 공부는 이렇게 하려고 한다. 문제를 풀다가 답지를 보는 느낌이라 뭔가 어제오늘 이틀동안 고군분투한 스스로에게 참 아쉽지만, 이해가 안 되는 부분이 있으면 정답을 보고 이해를 한 다음 내 것으로 만들면 된다는 마음가짐으로 용기있게(?) 참조하게 되었다.

다음과 같은 과정으로 공부해 볼 예정이다.

  1. reference code 의 작동 원리
    1. db model
    2. server model
    3. test case code
  2. 내가 작성한 code 의 작동 원리
    1. db model
    2. server model
    3. test case model
  3. 1과 2의 차이점을 파악
  4. reference code 를 어째서 그렇게 짰는지 생각해보기
  5. 내 코드는 왜 이렇게 짰는지 생각해보기(반성)
  6. 주석과 함께 차근차근 내 코드를 수정하기

Reference Code 의 작동 원리

DB model

CREATE TABLE messages (
  id INT NOT NULL AUTO_INCREMENT,
  roomname varchar(255),
  text text(1024),
  userid varchar(255) NOT NULL,
  createdAt datetime DEFAULT CURRENT_TIMESTAMP,
  updatedAt datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id)
);

/* Create other tables and define schemas for them here! */
CREATE TABLE users (
  id INT NOT NULL AUTO_INCREMENT,
  username varchar(255),
  createdAt datetime DEFAULT CURRENT_TIMESTAMP,
  updatedAt datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id)

messages 라는 table 은 수동으로 입력해서 넣어주어야 하는 column으로 roomname, text, userid 가 존재한다. 그리고 DEFAULT 값을 통해 자동으로 할당해주는 createdAtupdatedAt 이라는 column 이 있다. 마지막으로 AUTO-INCREMENT 속성이 있는 idPRIMARY KEY 로 사용한다.

users 라는 table 은 수동으로 입력해서 넣어주어야 하는 column으로 id, username 이 존재한다. 그리고 DEFAULT 값을 통해 자동으로 할당해주는 createdAtupdatedAt 이라는 column 이 있다. 마지막으로 AUTO-INCREMENT 속성이 있는 idPRIMARY KEY 로 사용한다.

Server Model

//db model
module.exports = {
  messages: {
    get: function(callback) {
      // fetch all messages
      // text, username, roomname, id
      var queryStr = "select messages.id, messages.text, messages.roomname, users.username
        from messages left outer join users on (messages.userid = users.id)
        order by messages.id desc";
      db.query(queryStr, function(err, results) {
        callback(err, results);
      });
    },
    post: function(params, callback) {
      // create a message for a user id based on the given username
      var queryStr = `insert into messages(text, userid, roomname)
                      value (?, (select id from users where username = ? limit 1), ?)`;
      db.query(queryStr, params, function(err, results) {
        callback(err, results);
      });
    }
  },
  users: {
    get: function(callback) {
      // fetch all users
      var queryStr = "select * from users";
      db.query(queryStr, function(err, results) {
        callback(err, results);
      });
    },
    post: function(params, callback) {
      // create a user
      var queryStr = "insert into users(username) values (?)";
      db.query(queryStr, params, function(err, results) {
        callback(err, results);
      });
    }
  }
};
//server model
var models = require("../models");

module.exports = {
  messages: {
    get: function(req, res) {
      models.messages.get(function(err, results) {
        if (err) {
          /* do something */
          res.send(err);
        }
        res.json(results);
      });
    },
    post: function(req, res) {
      var params = [req.body.text, req.body.username, req.body.roomname];
      models.messages.post(params, function(err) {
        if (err) {
          /* do something */
          res.send(err);
        }
        res.sendStatus(201);
      });
    }
  },

  users: {
    get: function(req, res) {
      models.users.get(function(err, results) {
        if (err) {
          /* do something */
        }
        res.json(results);
      });
    },
    post: function(req, res) {
      var params = [req.body.username];
      models.users.post(params, function(err) {
        if (err) {
          /* do something */
        }
        res.sendStatus(201);
      });
    }
  }
};

첫번째 경우 : /classes/messagesGET request 가 들어왔을 때

  1. messages.id, messages.text, messages.roomname, users.usernameSELECT 한다.
  2. messages table 을 기준으로 users table 과 LEFT JOIN 을 한다.
  3. 그리고 그 중에서 messages.userIdusers.username 이 같은 항목들을 추린다.
  4. 해당 queryString 을 통해 db.query 메서드로 쿼리를 발송한다.
  5. 그 결과물은 callback(err, results)messages.get 메서드의 인자로 받는 callback function 의 보내준다.
  6. 이제 그 메서드를 models 라는 이름으로 exports 하여 [messages.post](http://messages.post) 메서드에서 활용한다.
    1. models.messages.get 의 callback argument 로 express 의 request, response 함수를 넘겨준다
    2. 만약 에러가 존재한다면 (if (err)), res.send(err) 를 통해 에러를 담아 response 를 한다.
    3. 만약 에러가 존재하지 않는다면, body-parser.json(arg) 를 통해, results 를 JSON 형태로 바꾸어 response 를 한다.

두번째 경우 : /classes/messagesPOST request 가 들어왔을 때

  1. res.body 를 통해 POST 로 날아온 body 를 받는다.

  2. 그것을 params 라는 변수를 선언하고, 그 안에 req.body 의 각 항목을 배열 형태로 저장한다

    1. Schema Design 단계에서 직접 입력해 주어야 하는 column 은 roomname, text, userId 이고, 이 항목에 맞춰서 POST request 를 발송한다고 가정한다.
    2. params = [req.body.roomname, req.body.text, req.body.userId] 를 통해 배열 형태로 저장한다.
  3. 그리고 models.messages.get 의 인자로 params 와 query handler(가제)함수를 넘겨준다

    1. query handler 함수는 만약 먼저 에러를 감지해 에러가 존재한다면 res.send(err) 를 통해 에러를 담아 response 를 한다.
    2. 에러가 존재하지 않는다면, POST request 가 이상없이 잘 받아들여졌다고 201 상태코드를 담아 response 를 한다 (res.sendStatus(201))
  4. 위의 과정을 거쳐 만들어진 paramsquery handler 함수를, exports 를 통해 내보내서 인자로 받게 된 messages.get 함수를 활용한다.

    1. message.id, messages.text, messages.roomname, users.username 을 보내주기 위해 SELECT 로 먼저 선택한다
    2. 그리고, 다른 두 table 의 column data 를 보내야 하기 때문에 JOIN 을 활용한다.
      1. 그러나, 혹시라도 데이터간의 관계가 존재하지 않는 경우가 있을 수도 있기 떄문에 INNER JOIN 이 아닌 OUTER JOIN 을 활용하자 (여기서는 LEFT 를 사용했다)
    3. 그 중에서 user.idmessages.userId 항목이 같은 경우를 JOIN 의 조건으로 제한한다.
    4. 그렇게 해서 나온 쿼리문은, 다음과 같다
    SELECT messages.id, messages.text, messages.roomname, user.name
    FROM messages
    LEFT JOIN user ON messages.userId = users.id 
  5. 해당 쿼리문을 queryStr 로, POST 응답을 통해 인자로 넘겨받은 paramsqueryArg 로, 그리고 (err, results) 를 핸들링하는 함수에서는 callback 인자로 넘겨받은 query handler 에다가 에러가 발생하는 경우 3-1 시나리오처럼 res.send(err) 를 통해 에러를 담아 response 를 한다.

  6. 만약 에러가 존재하지 않는다면, 3-2 시나리오처럼 이상없이 잘 받아들여졌다고 201 상태코드를 담아 response 를 한다 (res.sendStatus(201))

번째 경우 : /classes/usersGET request 가 들어왔을 때

  1. messages.id, messages.text, messages.roomname, users.usernameSELECT 한다.
  2. messages table 을 기준으로 users table 과 LEFT JOIN 을 한다.
  3. 그리고 그 중에서 messages.userIdusers.username 이 같은 항목들을 추린다.
  4. 해당 queryString 을 통해 db.query 메서드로 쿼리를 발송한다.
  5. 그 결과물은 callback(err, results)messages.get 메서드의 인자로 받는 callback function 의 보내준다.
  6. 이제 그 메서드를 models 라는 이름으로 exports 하여 [messages.post](http://messages.post) 메서드에서 활용한다.
    1. models.messages.get 의 callback argument 로 express 의 request, response 함수를 넘겨준다
    2. 만약 에러가 존재한다면 (if (err)), res.send(err) 를 통해 에러를 담아 response 를 한다.
    3. 만약 에러가 존재하지 않는다면, body-parser.json(arg) 를 통해, results 를 JSON 형태로 바꾸어 response 를 한다.

일단 시간이 늦어서 여기까지만 하려고 한다. 내일은 상기한 과정들을 전부 끝내, 완성한 코드를 pull request 할 생각이다. 그리고 오늘 여기에서 만난 형과 express 로 구현한 socket 통신을 통해 캐치마인드처럼 서로가 서로의 그림을 볼 수 있는 웹페이지의 서버를 같이 검수(?) 했는데, 그 덕분에 socket 통신과 P5.js 라는 그림을 그리는 라이브러리를 알게 되었다. 여담으로 통신을 하는 경우, 실시간 통신 측면에서는 socket 통신이 좋지만 요즘은 거의 다 HTTP 방식의 통신을 이용한다는 썰도 들었다.

또 이걸 하면서 든 생각은 "토이프로젝트로 캐치마인드를 하나 만들어보는건 어떨까?" 였다. React 로 프론트를 구성하고, express 로 백엔드를 구성하고. DB는 채팅창을 옆에 만들어서 그 채팅들을 저장하는 식으로 하고. 상상만 해도 뭔가 재밌다!

뭐, 여튼. 그렇다. 오늘의 공부는 일단 여기서 마무리...


개형은 대략 이런 모습?

profile
правда и красота, truth and beauty

0개의 댓글