Backend/Node.js

[Node.js 교과서] ch12. NodeAuction 개선하기 2 : 서비스 분할

P.Venti 2023. 4. 5. 16:16

 

Node.js 교과서 2018년도 책 기준이라 개정된 책이랑 코드가 다릅니다.

 

1.0 들어가기 전

 

 프로그램 작성 시 코드를 하나의 파일에 작성하게 된다면 상당히 지저분해 가독성이 떨어지는 것은 물론 유지보수와 확장성마저도 떨어집니다. 물론 짧은 코드 경우에는 크게 상관이 없지만 코드가 길어지면 코드 분할은 불가피합니다. 

 코드 분할 시 원칙 몇 가지만 빠르게 설명드리고 NodeAuction 코드에 적용해 보겠습니다.

 

 1.1 중복되는 코드 최소화 

 중복되는 코드는 쉽게 의미 있는 함수로 만들어 사용할 경우 코드를 읽을 때 쉽게 이해할 수 있습니다. 

 

예시로 테스트 코드에서 헤더에서 쿠키를 읽어 csrf token을 발급받고 로그인 요청하는 코드입니다.

const request = require('supertest')
const app = require('../app')
... 생략 ...

const cookie = res.headers['set-cookie']
const csrfToken = cookie[1].slice(';')[0].slice('=')[1]

request(app)
  .post('/login')
  .set('Cookie', cookie)
  .send({
    username,
    password,
    _csrf : csrfToken
  })

 

위에서 csrfToken을 출력해서 요청값을 주는 경우는 빈번하게 발생할 것 같습니다. (csrfToken을 로그인 시에만 사용하지 않기 때문입니다.) 매번 요청할 때마다 이해하기도 난해한 체이닝 된 코드를 작성하는 것은 효율성이 떨어질 뿐만 아니라 코드 리딩시 가독성마저 떨어집니다. 

 

const request = require('supertest')
const app = require('../app')
... 생략 ...

/**
 *
 * 매개변수로 전달된 헤더에서 쿠키와 CSRF Token을 추출해 객체로 반환합니다.
 *
 * @param (Object) headers 응답받은 헤더
 * @returns cookie와 csrfToken이 포함된 Object
 */
const getCookieAndCSRFToken = (headers) => {
  const cookie = headers['set-cookie']
  const csrfToken = cookie[i].slice(';')[0].slice('=')[1]
  
  return {
    cookie,
    csrfToken
  }
|

const { cookie, csrfToken } = getCookieAndCSRFToken(res.headers);

request(app)
  .post('/login')
  .set('Cookie', cookie)
  .send({
    username,
    password,
    _csrf : csrfToken
  })

 

 위의 코드처럼 의미 있는 이름으로 함수를 만들어 매번 Cookie와 CSRF Token을 추출할 때마다 getCookieAndCSRFToken() 함수를 호출하게 재작성할 필요 없이 난해한 코드를 재사용 가능하고 읽을 때도 함수 이름만 읽어도 이해가 가니 가독성도 올라갑니다.

 

추가로 Visual Code에서는 위 예시처럼 주석을 작성해 줄 경우 해당 함수에 커서를 가져다 놓으면 아래와 같이 주석이 나옵니다.

 

 1.2 Service 계층 분할

 프로그램 로직에는 계층이 존재합니다. Node.js 에서는 Router 부분을 Controller 계층이라고 합니다. Controller 계층에서 비즈니스 로직(입력, 수정, 삭제 등 데이터베이스 연산 작업)을 Service계층으로 분리해 코드를 관리하는 것이 유지보수나 확장성면에서 좋습니다. 

 

 비즈니스 로직이 Controller 안에 있을 경우에 테스트 코드를 작성을 한다면 해당 Controller 계층에 접속하기 위해 supertest로 요청을 보내 결과를 받아와야 합니다. 하지만 비즈니스 로직이 Service 계층에 있다면 굳이 요청을 보낼 필요 없이 service 호출만으로도 테스트가 가능합니다.

 

 1.2.1 분할 전 테스트 코드 : 로그인 테스트

 

위의 소스 코드를 보시면 request 요청을 하시는 것을 확인하실 수 있습니다. 

 

 1.2.2 서비스 분할 후 테스트 코드 : 로그인 후 경매 물품 등록 테스트 

 

 서비스 분할 후 코드를 보시면 작성한 service를 불러와 요청 없이 해당 기능만 테스트하는 것을 확인하실 수 있습니다. 

 

2.0 서비스 분할

 

 NodeAuction 애플리케이션 소스코드는 분할하는 코드가 많기 때문에 일부만 예시로 보여주고 아래에 github 주소를 남겨놓겠습니다. 

 

 2.1 파일 구조

 serivce 관련 코드는 services 폴더에 아래와 같이 파일은 분리해 저장하겠습니다. 

 services/auction.js : 경매 관련 서비스

 services/auth.js : 회원관리 관련 서비스 

 

 2.2 비즈니스 로직 분할 

위에서 언급했듯이 Controller에 있는 데이터베이스 연산 로직을 serivce 쪽을 코드를 옮겨 작성해 줍니다. 

 

 2.2.1 회원가입 (service)

경로 :./service/auth.js

const { User } = require('../models')
const bcrypt = require('bcrypt')
const saltRound = 12;

module.exports = {
 /**
  * 
  * 회원 가입 서비스
  * 
  *  - password 암호화
  *  - 전달받은 user 정보를 저장
  * 
  * @param {object} userDTO email, nick, password, money
  * @param {Callback} done callback : err, success, info
  */
  join : async function(userDTO, done) {
    try{
        const exUser = await User.findOne({ where : { email : userDTO.email }})

        if(exUser) 
            done(null, false, { message : "이미 가입된 이메일입니다."})

        const hash = await bcrypt.hash(userDTO.password, saltRound)

        userDTO.password = hash;

        await User.create(userDTO)

        done(null, true)

    }catch(err){
        done(err, false) 
    }
  }
  ... 생략 ...
}

 

 2.2.2 회원가입(Controller POST /join)

경로 :./routes/join.js

 

코드가 난잡해 보일 수 있어 이 전 글에서 작성한 입력값 검증 코드는 제외하고 보여드리겠습니다. 

 

... 생략 ...
const AuthService = require('../services/auth')

router.post('/join', isNotLoggedIn, async (req, res, next) => {
  try{
    const userDTO = Object.assign(req.body)
    
    // req.body에 있는 csrf Token 제거
    delete userDTO._csrf
    
    // auth service에서 회원가입 서비스 호출
    AuthService.join(userDTO, (err, success, info) => {
      if(err) return next(err)
      if(!success && info) {
        req.flash('joinError', info.message)
        return res.redirect('/join')
      }

      return res.redirect('/')
    })
  catch(err){
    next(err)
  }
})

... 생략 ...

 

 

 코드 분할 후 코드를 읽어보시면 확실히 Controller 내에 어떤 기능을 하는지 눈으로 잘 읽힙니다. 또한 테스트할 경우 위에 예시에서도 봤듯이 확실히 매 테스트마다 불필요한 요청과정이 빠지니 유지보수면에서도 훨씬 개선됐습니다. 

 

 

 

전체 코드(Github)

 

 

GitHub - dotredbee/NodeAuction_update: Node.js교과서 ch12 코드에서 추가로 업데이트한 서버입니다.

Node.js교과서 ch12 코드에서 추가로 업데이트한 서버입니다. Contribute to dotredbee/NodeAuction_update development by creating an account on GitHub.

github.com

 

 어느 정보 서비스 분할이 끝났으니 다음 글에서는 Node.js 교과서에 나오는 스스로 해보기 문제를 해결해 보겠습니다. 

오늘도 읽어주셔서 감사합니다.

 

잘못된 정보는 언제든 지적해 주시면 감사합니다.

공감과 댓글은 언제든 환영입니다.