Node | Layered Pattern

이진웅·2021년 12월 20일
0

Node

목록 보기
1/1
post-thumbnail

모듈화가 필요한 이유

한 파일에 모든 코드를 다 넣어도 노드 서버는 문제 없이 동작한다. 간단한 규모의 어플리케이션이라면 굳이 모듈화가 필요 없지만, 우리는 실제 서비스에서 사용할 수 있는 어플리케이션을 구현하는 것이 목표다. 즉, 혼자서 개발하는 것은 극히 드물다.

결국, 좋은 코드(협업 가능한 코드)를 작성하는 것이 좋은 개발자의 역할이다. 그렇다면 좋은 코드, 협업 가능한 코드는 무엇일까?

크게 다섯가지의 카테고리로 분류할 수 있다. 1)

  • 확장성(extensibility)
    확장성을 고려하지 않은 코드는 시스템의 규모가 커질수록 문제가 생길 확률이 높다.

  • 재사용성(reusability)
    반복되는 로직을 함수로 분리하는 코드상의 재사용성 뿐만 아니라, 우리가 설계한 구조가 재사용 되어야 한다.

  • 유지-보수 가능성(maintability)
    여러 로직이 뒤엉켜 있는 코드는 유지 보수가 안된다.

  • 가독성(readability)
    어려운 로직 일수록 더 가독성이 높아야 한다. 어려운 로직을 쉽고 간단하게 구현하는 것이 좋은 코드다.
    프로젝트의 구조 또한 한 눈에 그려져야 한다.

  • 테스트 가능성(testability)
    테스트를 하기 쉬운 코드는 모듈화가 잘 되어 있고, 한 가지 역할만 하는 함수 단위의 코드를 의미한다.
    프로젝트의 구조도 추상화가 잘 되어있고, 역할이 잘 나뉘어 있는 구조가 테스트하기 쉬운 구조다.

MVC pattern

다섯가지의 카테고리에 맞게 코드의 구조를 구상할 수 있는 패턴 중 하나가 MVC 패턴이다.
MVC 패턴은 각각 Model, View, Controller를 의미한다.

View

SPA(Single Page Application)과 Mobile App 의 빠른 성장으로 FrontEnd 개발자의 역할이 커지면서 서비스를 위한 소프트웨어는 FrontEnd 와 BackEnd 로 나뉘게 되었다. 본래 MVC는 서버 사이드에서 한번에 다뤄지던 구조다. 예를 들어, Django 의 템플릿 기능이나 Express 에도 ejs 를 사용하면 View 를 구현할 수 있다.

View는 쉽게 말해서 클라이언트(유저)와 상호작용이 일어나는 것을 의미한다. 즉, 화면에 보여주기 위한 역할을 하는 것이다. User Interface(유저 인터페이스)가 바로 View 레이어에 있는 코드로 핸들링 된다. React, Angular, Vue 같은 라이브러리 또는 프레임워크로 개발하는 앱을 생각하시면 된다. 모바일 iOS, 안드로이드 앱도 View 를 담당한다고 할 수 있다.
(프론트엔드와 백엔드를 모두 포함하는 하나의 서비스 관점에서 보았을 때)

MVC 패턴은 FrontEnd 에서 프로젝트를 관리 할 때에도 사용될 수 있는 레이어링 패턴이다
모든 소프트웨어 로직에 적용가능한 패턴임을 기억하도록 하자!

Controller

View 레이어에서 유저의 동작이 닿는 곳은 대부분 데이터의 변화가 필요하다. 예를 들어, 내가 담은 장바구니를 보고싶을 때 장바구니 버튼을 클릭하게 되고, 이 때 HTTP 요청이 백엔드 서버로 보내진다. 이 요청을 받아서 처리하는 곳이 컨트롤러다.

컨트롤러는 View(유저 인터페이스 레이어)Model(데이터를 담당하는 레이어)을 잇는 다리 역할을 하는 부분이다. 유저의 요청을 처리해서 응답하는 부분이라고 할 수 있다. Controller 는 Model 과 소통하게 된다.

Model

서비스에 필요한 모든 데이터는 모델에서 정의된다. 오로지 Model 레이어에 정의된 데이터베이스 schema(모델 또는 테이블)를 통해서만 데이터베이스에 접근해서 CRUD 로직을 처리할 수 있다.

MVC Pattern 의 장점

  • 염려의 분리 (Seperation of Concerns)
    유저 인터페이스와 관련된 부분은 모두 View 에 의해서 관리되고, 모든 데이터와 관련된 로직은 Model 에 의해서 관리되며 오로지 Controller 에 의해서 Model 에 접근할 수 있게 됩니다. 각각의 레이어가 하는 역할이 명확하다.

  • 동시적인 개발 (Simultaneous Development)
    세개의 레이어로 역할이 나뉘어져 있기 때문에 동시다발적인 개발이 가능하다. 역할분담에 용이하며 협업이 가능한 프로젝트를 구성할 수 있다.

  • 수정의 용이함 (Ease of Modification)
    다른 레이어에 영향을 주지 않고 문제가 있는 로직을 찾아서 문제를 해결할 수 있다.

  • 테스트-주도 개발(Test Driven Development)
    각각의 레이어, 그리고 그 레이어 속에 속한 각각의 모듈을 테스트 하기 좋다.

Node.js Project Layering

Node.js 를 통한 BackEnd 어플리케이션은 큰 서비스의 관점에서 보았을 때 Controller 와 Model 의 레이어를 담당하게 된다. 하지만 오로지 이 두개의 레이어로만 로직을 분리하기에는 코드의 복잡성과 레이어 하나가 담당하는 비중이 너무 커지기 때문에 더 확장해서 프로젝트 코드를 레이어링 해야한다.

Route, Controller, Service, Model 각각의 레이어가 하나의 폴더이자 역할을 의미한다.

  1. 큰 박스에서 작은 박스로 갈 수록 더 데이터를 다루는 로직(데이터베이스 접근하는 로직)에 가깝게 된다.
  2. 또한, 각각의 레이어는 오로지 바로 아래에 있는 레이어에만 의존하게 된다.
    • Route → Controller
    • Controller → Service
    • Service → Model

예를들어, Route 는 Service 로직을 전혀 모릅니다. 아예 관여 조차 하지 않다.
따라서, Service 로직을 변경해도 Route 와 Controller 의 코드는 바뀔 필요가 없다.

즉, 다음과 같은 상황에서 유연하게 대처할 수 있다는 의미이다.
때때로, 서비스를 구현하다가 RDBMS(관계형 데이터 베이스) → NoSQL(ex. mongoDB) 로 이전하는 경우가 있는데, Route와 Controller 의 로직은 전혀 바뀌지 않은채로 데이터를 다루는 Service 와 Model 의 로직만 변경 해 주면 된다.

// index.js
const express = require('express');
const router = express.Router();

const userRouter = require('./userRouter');

router.use('/users', userRouter);

module.exports = router;
// router.js
const express = require('express');
const router = express.Router();

const userController = require('../controllers/userController');

router.post('/signin', userController.signIn);
router.post('/signup', userController.signUp);

module.exports = router;
// controller.js
const userService = require('../services/userService');

const signIn = async (req, res) => {
  try {
    const { email, password } = req.body;
    const REQUIRED_KEYS = { email, password };

    for (let key in REQUIRED_KEYS) {
      if (!REQUIRED_KEYS[key]) {
        return res.status(400).json({ message: `KEY_ERROR: ${info}` });
      }
    }
    console.log('email in controller: ', email);

    const token = await userService.signIn(email, password);

    console.log('user in controller: ', token);
    
    return res.status(200).json({ message: 'Login_success', token });
  } catch (err) {
    console.log(err);
    return res.status(err.statusCode || 500).json({ message: err.message });
  }
};

module.exports = { signIn };
// service.js
const userDao = require('../models/userDao');

const signIn = async (email, password) => {
  const [user] = await userDao.getUserByEmail(email);

  console.log('user in service: ', user);
  if (!user) {
    const error = new Error('Invalid User');
    error.statusCode = 400;
    console.log('email: ', email);
    throw error;
  }
  if (user.password !== password) {
    const error = new Error('Invalid User');
    error.statusCode = 400;
    console.log('password: ', password);
    throw error;
  }
  const token = '1234';

  return token;
};

module.exports = { signIn };
// model.js
const prisma = require('./index');

const getUserByEmail = async (email) => {
  const user = await prisma.$queryRaw`
    SELECT email, password FROM users WHERE email = ${email}
  `;
  console.log('users in dao: ', user);
  return user;
};

module.exports = { getUserByEmail };

출처

1) 깔끔한 파이썬 탄탄한 백엔드 - 송은우 저

0개의 댓글