210816. Today I Learned(TIL) : Sequelize ORM-shortly sprint review(Part 2)

syong·2021년 8월 16일
1

TIL

목록 보기
24/32
post-custom-banner

Controller 구현

조금 많이 늦어버린 스프린트 리뷰..가 되어버렸지만, 다가올 HA를 위해 지금이라도 정리해두자!

Part 2 requirement는 controller 구현이다. 총 세 개의 함수를 구현해야 하는데, 구현해야 할 함수는 get, getById(redirect),post 이렇게 3가지 이다.

GET 요청

우선 shortly 서버의 기능은, post 요청한 원래의 url을 대체할 짧은 url로 만들어 반환해주는 것이 주요 기능이다. 하지만 post 구현 전에 가장 기본적인 get 요청을 먼저 처리하도록 한다.

지금부터는 내가 작성한 코드와 레퍼런스 코드를 비교 분석하면서 진행하겠다.

내가 작성한 코드

    get: async (req, res) => { // models의 url에서 모든 테이블 목록을 찾는 요청을 하여 모든 응답을 받는다.
        const data = await models.url.findAll();
        // console.log("data------", data)
        res.status(200).send(data);
	},

reference

  get: async (req, res) => {
    const result = await URLModel.findAll();
    res.status(200).json(result);
  },

요청한 데이터를 필터 없이 전부 가져오는 get 구현은 굉장히 간단하다. 따라서 내 코드와 레퍼런스 코드 간의 차이가 거의 없다. require 받아온 models의 변수명이 다른 것과 마지막 response에서 json()을 사용했냐 send()를 사용했냐의 차이만 있을 뿐, 로직 상의 큰 차이는 없다.

GET BY ID(REDIRECT)

앞서 먼저 구현했던 기본 get 요청과 달리 이번에는 요청한 id와 일치하는 url 주소를 받아오는 기능이다. 만약 기존에 데이터베이스에 저장되어있던 url이라면 저장된 url을 반환하고 방문횟수 카운팅(visits + 1)를 하지만, 요청한 url id값이 존재하지 않는다면 즉, 처음 요청한 url이라면 찾는 정보가 없음(204 code)를 반환해야 한다.

내가 작성한 코드

    findOneValue: async (req, res) => { // id를 PK로 하여 id를 요청하면 visits이 1씩 증가한다.
        const id = req.params.id;
        const url = await models.url.findOne({ // findOne 메소드 사용
            where: {
                id: id
            }
        });
        //.findByPk(id); -> findByPk 메소드 사용도 같은 결과를 도출함
        // console.log("url------", url);
        await url.increment('visits', {by:1});
        res.redirect(url.url); // visits이 증가한 url을 다시호출.
	},

reference

// then 사용
  redirect: (req, res) => {
    URLModel
      .findOne({
        where: {
          id: req.params.id
        }
      })
      .then(result => {
        if (result) {
          return result.update({
            visits: result.visits + 1
          });
        } else {
          res.sendStatus(204);
        }
      })
      .then(result => {
        res.redirect(result.url);
      })
      .catch(error => {
        console.log(error);
        res.sendStatus(500);
      });
  }
  // async await 사용
    redirect: async (req, res) => {
    const urlId = req.params.id;
    const result = await URLModel.findOne({
      where: {
        id: urlId,
      },
    });
    if (!result) {
      return res.sendStatus(204); //찾는게 없습니다...
    }
    try {
      await result.update({
        visits: result.visits + 1,
      });
      const redirectUrl = result.url;
      res.redirect(redirectUrl);
    } catch (err) {
      res.sendStatus(500);
    }
  }

여기서부터 차이점이 많이 발견된다. 우선 레퍼런스는 두 가지 버전이 있다. 하나는 then으로 엮은 코드, 다른 하나는 async await을 사용한 코드이다.

  1. 내 코드는 에러 처리가 전혀 이루어지지 않았다. 지금은 이렇게 작성해도 테스트 케이스 통과가 목적이기 때문에 상관없어 보이지만, 나중에 실무에서 이렇게 구현한다면 에러처리가 되지 않아 문제가 발생할 수 있다. 반면 레퍼런스는 찾는 id값이 존재하지 않으면 204 code로 응답하도록 구현되어 있다. 클라이언트쪽 요청에러는 204를 리턴, 마지막 then까지 실행한 후에도 204를 리턴하거나 찾는 url을 리턴하지 못하게 되면 서버쪽 에러 코드 500을 리턴하도록 되어 있다.

  2. 내 코드는 visits 증가 처리를 increment 메소드로 처리한 반면, 레퍼런스는 update 메소드로 처리했다.

    • increment 메소드와 update 메소드의 차이를 살펴보면 다음과 같다.
      • sequeilize API 문서에서 정의하는 increment 메소드의 사용법은 다음과 같다.

        여기에서 options: object 인자에 들어갈 object의 조건도 있다.

        세 번째 라인을 보면 number타입을 변화시키려면 by라는 키를 사용해야 한다고 나와있다.
      • 반면 update 메소드의 사용법은 다음과 같으므로 사용할 때 각 메소드가 받는 인자를 적절히 삽입하여 실행해주어야 한다.

POST

post 요청은 req.bodyurl을 담아 요청한다. 만약 요청받은 url이 기존 데이터베이스에 저장되어있던 url이라면 저장된 url을 반환하지만, 요청한 url이 존재하지 않는다면 즉, 처음 요청한 url이라면 데이터베이스에 해당 url 정보를 저장하고 201을 리턴해야 한다.

내가 작성한 코드

post: async (req, res)=> { // models의 url에서 url과 title을 따로 (getUrlTitle 함수가) 생성해서 응답한다. - 단축URL
		const url = req.body.url;
            getUrlTitle(url, async (err, title) => {
            // console.log("url------", url);
            const urlModel = await models.url.create({ url, title });
            res.status(201).send(urlModel);
		});
	}

reference

// then 사용
  post: (req, res) => {
    const { url } = req.body;

    if (!utils.isValidUrl(url)) {
      return res.sendStatus(400);
    }

    utils.getUrlTitle(url, (err, title) => {
      if (err) {
        console.log(err);
        return res.sendStatus(400);
      }

      URLModel
        .findOrCreate({
          where: {
            url: url
          },
          defaults: {
            title: title
          }
        })
        .then(([result, created]) => {
          if (!created) { // created가 false라면(이미 존재하는 데이터라면)
            return res.status(201).json(result); // find
          }
          res.status(201).json(result); // Created
        })
        .catch(error => {
          console.log(error);
          res.sendStatus(500); // Server error
        });
    });
  },
    // async await 사용
      post: async (req, res) => {
    const url = req.body.url;
    if (!isValidUrl(url)) {
      return res.sendStatus(400);
    }
    getUrlTitle(url, async (err, title) => {
      if (err) {
        return res.sendStatus(400);
      }
      try {
        const [result, created] = await URLModel.findOrCreate({
          where: {
            url,
          },
          default: {
            title,
          },
        });
        if (created) {
          return res.status(201).json(result); // 새로 생성된 경우 create
        }
        res.status(201).json(result); // 조회한 경우 find
      } catch (error) {
        console.log(error);
        res.sendStatus(500);
      }
    });
  },
  1. 우선 두 버전 코드와 내 코드 간의 공통적인 차이점은 역시나 에러 처리이다. 에러처리 과정은 위에서 설명한 getById와 동일하므로 설명은 생략하겠다.
  2. 이번 스프린트에는 modules dir에 utils.js라는 파일이 존재한다. 이 파일 안에는 메소드를 구현할 때 필요할 것 같은 함수들을 미리 만들어 놓고 따로 분리해둔 파일이다. 내 코드에서는 사용되지 않았던 isValidUrl(url) 함수가 데이터 베이스 안에 존재하는 url인지 아닌지를 판별해 boolean값으로 리턴한다. 내 코드에서는 이미 존재하는 url인지 아닌지의 판별 자체를 하지 않았기 때문에 사용되지 않을 수밖에 없었다. 이 또한 실무에서 구현할 때는 치명적인 실수가 될 수 있기 때문에 주의해야 한다.
  3. 내 코드에서는 create() 메소드를 사용했지만 레퍼런스에서는 findOrCreate() 메소드를 사용했는데, 이 역시 2번에서 설명한 isValidUrl(url) 함수를 사용하지 않은 문제와 동일한 문제로 메소드 사용에서 차이가 발생했다. findOrCreate() 메소드는 조회 후에 없는 값이면 생성하고, 있는 값이면 그 값을 가져오는 기능이기 때문에 단순히 create() 작업만 해주는 것과 차이가 있다.

Routes에 함수 연결

최종적으로 모든 함수 구현이 끝나면 각 함수를 다음과 같이 links.js 파일과 연결하여 query에 알맞는 함수가 실행될 수 있도록 해주면 모든 구현이 끝이 난다.

const linksController = require('../controllers/links');

router.get('/', linksController.get);
router.get('/:id', linksController.findOneValue);
router.post('/', linksController.post);


module.exports = router;
post-custom-banner

0개의 댓글