본문 바로가기

Side Project

[PROJECT] Trello Clone 프로젝트 API - (5) Sequelize 관련 정리

[PROJECT] Trello Clone 프로젝트 API - (5) Sequelize 관련 정리

서론

API 코드 작성에 완료했다. 개발 도중에 블로그 글을 작성하려고 하니 기존에 블로그에 적었던 내용도 많이 바뀌고 중복되는 코드도 많아 어려움이 있었다. 이번 포스팅에서는 코드를 작성하면서 생각해본 Sequelize 팁 같을걸 정리해보려고 한다.

내가 생각하는 Sequelize 사용 팁

transaction

트렌젝션은 데이터베이스가 상태를 변화시키기 위해서 수행하는 작업의 단위를 뜻한다. Sequelize 에서는 트렌젝션 기능을 제공해주는데 사용 방법은 2가지가 있다.

  • Managed transaction
  • Unmanaged transaction

둘의 차이는 자동으로 commit, rollback을 해주냐 차이인데 commit 은 해당 트렌젝션이 성공하였을 때 호출하는 메서드이고, rollback은 해당 트렌젝션이 실패하였을 때 호출하는 메서드인데, 첫번째 방법인 Managed transactioncommitrollback을 자동으로 해준다.

  • commit : 해당 트렌젝션이 성공하였을때 호출하는 메서드
  • rollback : 해당 트렌젝션이 실패하였을때 호출하는 메서드

Managed transaction 관련 예제

models.sequelize.transaction(transaction => {
  t = transaction
  return Board.findOne({
    where: {
      bid,
    },
    transaction: t
  })
    .then(boardCheck)
    .then(memberCheck)
    .then(isMember)
}).then(respond) // 여기서 자동으로 commit
  .catch(onError) // 여기서 자동으로 rollback

반면에 Unmanaged transaction는 사용자가 직접 commitrollback을 해주어야 한다. 소스코드를 보면 바로 이해가 될것 이다. 위 코드를 Unmanaged transaction 방식으로 작성해 보겠다.

Unmanaged transaction 관련 예제

models.sequelize.transaction().then(t => {
  t = transaction
  return Board.findOne({
    where: {
      bid,
    },
    transaction: t
  }).then(boardCheck)
    .then(memberCheck)
    .then(isMember)
}).then(() => {
      t.commit() // 여기서 수동으로 commit
})
.catch((err) => {
    t.rollback() // 여기서 수동으로 rollback
}) 

추가 (create)

유저를 추가시킨다거나 보드, 리스트, 카드 등을 추가하는 동작에 사용하는 create는 메서드는 옵션이 여러가지이다.

파일 상단에서 모델들을 정의하고

const models = require('../../models')
const User = models.db.user

추가시키는 url 요청이 오면 일단 검사를 한다. 예를 들어 카드를 추가시키는 요청을 예로 들면

  • 해당 유저가 존재하는 유저인지 ( 실패시 : 401 )
  • 카드를 추가시키는 리스트가 존재하는 리스트인지 ( 실패시 : 404 )
  • 해당 유저가 해당 보드에 속해있는 멤버인지 ( 실패시 : 403 )

위에 내용들을 함수로 만들어 성공시 resolve, 실패시 에러를 발생시켰다.

위에 조건들을 모두 통과하면 추가하는 로직을 실행시켜준다.

[모델명].create({
  title,
  lid,
  description,
  position,
  등등등...
},{
  transaction: t // transaction
})

create는 인자를 2개를 받는다. 첫번째는 추가시키는 칼럼들이 담긴 객체, 두번째로는 추가시키는 옵션이 들어간다.

그리고 방금 create 한 데이터를 가지고 오고 싶다면 해당 createPromise 로 반환한 뒤 then 에서 받은 데이터를 dataValues로 접근하면 방금 추가시킨 칼럼 데이터를 가져올 수 있다.

const add = () => {
  return [모델명].create({
    title : "PAPICO blog",
    name : "PAPICO",
    id: "papico",
    등등등...
  },{
    transaction: t // transaction
  })
}

const respond = (data) => {
    const {title, name, id} = data.dataValues
    console.log(title, name, id)
}

add()
  .then(respond)
  .catch(onError)

조회 (findOne, findAll)

보드리스트를 조회하는 로직에서는 findOne 이나, findAll을 사용한다. 한개의 데이터 row를 가져올 때는 findOne, 두개 이상을 가져올때는 findAll을 사용한다.

실제 사용코드는 다음과 같다.

모델명.findAll({
  where: {
    name: "PAPICO"
  }, // where 문
  order: [
    ['created_at', "DESC"]
  ], // "정렬 기준"
  include: [{
    model: User, // 조인할 테이블
    attributes: ['username', 'photo', 'email'] // 조인할 테이블에서 가져올 칼럼
  }], // join 
  transaction: t // transaction
})

이 코드에서 주위할 점이 있다면 그전에 create 메서드나 나중에 나올 update 메서드는 인자를 2개 받지만 findOne 이나 findAll 은 인자를 한게만 받기 때문에 transaction을 할 때 주의해야한다.

수정 (update)

update는 사용자가 비밀번호를 바꾸거나, 게시물을 수정할 떄 사용하는 메서드로 create와 비슷하다. 다만 다른 점은 where을 사용하여 조건에 맞는 데이터를 수정하는 것이다.

[모델명].update({
  title,
  description
},{
  where: {
    cid // 조건
  },
  transaction: t // transaction
})

삭제 (destroy)

destroy 는 삭제를 할때 사용하는 메서드로 사용방식을 조회할떄 코드와 많이 비슷하다.

[모델명].destroy({
  where: {
    cid
  },
  transaction: t
})

Sequelize 는 아니지만 유용했던 코드

Promise.all() 메서드를 보드에 속해있는 리스트 목록을 조회할때 사용하였다.
일단 로직은

  • 보드의 존재 여부 판단
  • 해당 유저가 보드의 멤버인지 조회
  • 리스트가 없다면 204 반환
  • 리스트가 있다면 리스트 반환

해당 로직을 구현하기 위해서는 일단 보드 조회 쿼리 한번, 멤버 쿼리 한번, 리스트의 조회 1번 3번 의 쿼리를 날리는데 조건도 여러가지이다.

그래서 생각해낸 방법이 Promise.all()을 이용하는 것이였다.
사용법은

Promise.all([함수1, 함수2]).then(data => {
    console.log(data) // [함수1의 반환값, 함수2의 반환값]
})           

인자로 함수 리스트를 받고 성공시 then 으로 반환값들을 배열형태로 가져올수 있다.


const getBoard = (req, res) => {
    let t
    const decoded = req.decoded
    const {bid}  = req.params

    const boardCheck = new Promise((resolve, reject) => {
        Board.findOne({
            where: {
                bid
            },
            transaction: t,
            attributes: [],
            include: [{
                model: List,
                order: [
                    ['position']
                ],
            }]
        }).then(board => {
            resolve(board)
        })
    })

    const memberCheck = new Promise((resolve, reject) => {
        Member.findOne({
            where: {
                uid: decoded.uid,
                bid
            },
            transaction: t
        }).then(member => {
            resolve(member)
        })
    })

    const respond = (board) => {
        if(board == null) {
            res.status(204).send()
        } else {
            res.json({
                result: true,
                data: board
            })
        }

    }

    const onError = (error) => {
        console.error(error)
        res.status(400).json(ErrorHandler(error.message))
    }

    models.sequelize.transaction(transaction => {
        t = transaction
        return Promise.all([boardCheck, memberCheck]).then(data => {
            const [board, member] = data
            if(!board) { // 1. 보드 존재 여부
                throw new Error("NOTFOUND")
            } else if(!member) { // 2. 해당 멤버 여부
                throw new Error("FORBIDDEN")
            } else if(board.lists.length == 0) { // 3. 리스트 값 체크
                return null
            } else { // 4. 리스트 반환
                return board.lists
            }
        })
    }).then(respond)
    .catch(onError)
}

이런 식으로 boardCheck, memberCheck 처럼 Promise 를 반환하는 함수들을 이용해 반환값들을 한번에 검사하였다.

마치며

API 는 개발 완료했다. 원래 계획은 기능을 전부 쪼개서 하나하나 올릴 계획이었는데 너무 중복되는 로직이 많아서 이런식으로 설명할 수 밖에 없었다. 현재 Vue 를 이용하여 클라이언트를 구현하는 중이다. 회원 관련 client 파트는 전부 구현했지만 변경 가능성이 있어 일단 다음 포스팅 부터는 최근에 인프런 강의로 듣고있는 함수형 프로그래밍을 정리해보려고 한다. 완성한 API 소스는 깃헙 링크를 첨부하겠다.

관련 링크

API 소스코드 링크 https://github.com/ocipap/API__Trello-clone-project