한 파일에 모든 코드를 다 넣어도 노드 서버는 문제 없이 동작한다. 간단한 규모의 어플리케이션이라면 굳이 모듈화가 필요 없지만, 우리는 실제 서비스에서 사용할 수 있는 어플리케이션을 구현하는 것이 목표다. 즉, 혼자서 개발하는 것은 극히 드물다.
결국, 좋은 코드(협업 가능한 코드)를 작성하는 것이 좋은 개발자의 역할이다. 그렇다면 좋은 코드, 협업 가능한 코드는 무엇일까?
크게 다섯가지의 카테고리로 분류할 수 있다. 1)
다섯가지의 카테고리에 맞게 코드의 구조를 구상할 수 있는 패턴 중 하나가 MVC 패턴이다.
MVC 패턴은 각각 Model, View, Controller를 의미한다.
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 에서 프로젝트를 관리 할 때에도 사용될 수 있는 레이어링 패턴이다
모든 소프트웨어 로직에 적용가능한 패턴임을 기억하도록 하자!
View 레이어에서 유저의 동작이 닿는 곳은 대부분 데이터의 변화가 필요하다. 예를 들어, 내가 담은 장바구니를 보고싶을 때 장바구니 버튼을 클릭하게 되고, 이 때 HTTP 요청이 백엔드 서버로 보내진다. 이 요청을 받아서 처리하는 곳이 컨트롤러다.
컨트롤러는 View(유저 인터페이스 레이어)와 Model(데이터를 담당하는 레이어)을 잇는 다리 역할을 하는 부분이다. 유저의 요청을 처리해서 응답하는 부분이라고 할 수 있다. Controller 는 Model 과 소통하게 된다.
서비스에 필요한 모든 데이터는 모델에서 정의된다. 오로지 Model 레이어에 정의된 데이터베이스 schema(모델 또는 테이블)를 통해서만 데이터베이스에 접근해서 CRUD 로직을 처리할 수 있다.
Node.js 를 통한 BackEnd 어플리케이션은 큰 서비스의 관점에서 보았을 때 Controller 와 Model 의 레이어를 담당하게 된다. 하지만 오로지 이 두개의 레이어로만 로직을 분리하기에는 코드의 복잡성과 레이어 하나가 담당하는 비중이 너무 커지기 때문에 더 확장해서 프로젝트 코드를 레이어링 해야한다.
Route, Controller, 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) 깔끔한 파이썬 탄탄한 백엔드 - 송은우 저