오늘도 개발
Layered Pattern 본문
1. Layered Pattern이란?
레이어드 패턴이란?
코드를 목적에 따라 분리한 후 각각 다른 레이어에 작성하는 방식을 레이어드 패턴이라고 한다.
코드를 목적에 따라 분리하는 것을 관심사의 분리라고 한다.
모든 직원이 모든 것을 다 해야하는 식당이 있다고 생각해보자.
모든 직원은 고객도 맞고 주문도 받고 재료도 찾아 요리도 해야한다.
이 경우 혼동이 발생할 수도 있고 효율이 떨어질 수도 있다.
실수가 생겼을 때 주문을 잘못 받았는지 요리를 잘못했는지, 어디서 오류가 났는지 알기 어렵다.
업무를 나눠 각 직원이 각자 잘하는 일을 맡아서 하면 이런 문제를 해결할 수 있다.
그럼 웨이터는 서빙만, 요리사는 요리만 하면 된다.
프로그래밍도 마찬가지다.
한 파일이 모든 것을 다 처리한다면 디버깅이 어렵고 비효율적이다.

이 경우 역시 프로그램이 할 업무를 나누고,
업무별로 파일(=레이어)을 만들어 각 업무를 맡도록 하면 효율적일 것이다.

레이어드 패턴의 장점
1) 각 구성 요소의 역할이 명확해진다.
2) 각 레이어는 독립적이므로 서로에게 미치는 영향이 최소화된다.
3) 가독성이 높아진다.
4) 재사용 가능성이 높아진다.
특징 1 - 단방향성
레이어드 패턴의 레이어는 한 쪽의 레이어에게만 의존함.
예를 들어 presentation layer는 business layer의 모듈만 import함.
business layer는 persistence layer의 모듈만 import함.
특징 2 - SoC(Seperation of Concern)
코드를 목적에 따라 분리시킴.
2. 레이어
백엔드의 레이어는 보통 다음과 같이 나눌 수 있다.
MVC 패턴의 View가 프론트엔드로 독립하면서 M, C를 좀 더 세분화해서 세 레이어로 나눈다.


1) Presentation Layer(=Controller)
클라이언트와 직접적으로 연결되는 레이어.
API 엔드포인트 정의.
HTTP 요청을 읽는 로직 구현
2) Business Layer(=Service)
비즈니스 로직을 구현하는 레이어.
실제로 시스템이 해야 할 일을 구현함(ex. 비밀번호에 특수문자가 없으면 회원가입 거부)
3) Persistence Layer(=Model)
데이터베이스와 관련된 로직을 구현하는 레이어.
데이터를 생성, 수정, 읽어서 데이터베이스에서 CRUD 진행
3. 구현 예시
레이어드 패턴 적용 전
구조
.
|_ server.js
server.js
app.js의 signUp()이라는 함수는 서버 켜기, 요청/응답 처리, 비즈니스 로직 처리, 데이터베이스 로직 처리를 모두 하고 있다.
const dotenv = require('dotenv');
dotenv.config(); // 전 프로젝트 어느 곳에서나 이제 .env에 접근 가능
const http = require('http');
const express = require('express');
const app = express()
app.use(express.json())
app.post('/signup', async(req, res) => {
// Controller 역할(요청 컨트롤)
const { username, password } = req.body;
const hasKey = { username: false, password : false };
const requiredKey = Object.keys(hasKey);
Object.entries(req.body).forEach((keyValue) => {
const [ key, value ] = keyValue
if (requiredKey.includes(key) && value){
hasKey[key] = true;
}
})
const hasKeyArray = Object.entries(hasKey);
for (let i = 0; i < hasKeyArray.length; i++){
const [key, value] = hasKeyArray[i]
if (!value){
res.status(400).json({message: `키(${key})가 존재하지 않습니다.`})
}
}
// Service 역할(비즈니스 로직)
const salt = bcrypt.genSaltSync(12);
const hashedPw = bcrypt.hashSync(password, salt)
try {
// Model 역할(DB 로직)
await myDataSource.query(
`INSERT INTO users (username, password) VALUES (?, ?)`,
[username, hashedPw]
)
// Controller 역할(응답 컨트롤)
res.status(201).json({message: "userCreated"})
}
catch(err) {
console.log(err)
res.status(500).json({message: 'error'})
}
})
const server = http.createServer(app)
server.listen(8000, () => {
console.log('server is listening on port 8000');
})
레이어드 패턴 적용 후
구조
.
|_server.js
|_app.js
|_.env
|_.env.test
|_controllers
|____userController.js
|_services
|____userService.js
|_models
|____userDao.js (Dao는 DataAccessObject의 줄임말)
|_routers
|____index.js
|____userRouter.js
|_tests
|____user.test.js
1) app.js
앱 관련 함수만 넣음
const morgan = require('morgan');
const cors = require('cors');
const express = require('express');
const router = require("./routers");
const createApp = () => {
const app = express();
app.use(morgan('dev'));
app.use(express.json());
app.use(router);
app.use(cors());
return app;
}
module.exports = { createApp };
2) server.js
서버 구동 관련 함수만 넣음
const http = require('http')
const dotenv = require('dotenv')
dotenv.config()
const { createApp }= require('./app')
const app = createApp()
const server = http.createServer(app)
server.listen(8000, () => {
console.log('server is listening on PORT 8000')
})
3) routers
<index.js>
요청이 들어오면 일단 거치는 곳. 여기서 각 라우터로 연결해줌.
const express = require('express');
const router = express.Router();
const userRouter = require('./userRouter');
// uri에 users가 있으면 userRouter 파일로 보내줌
router.use('/users', userRouter);
// module.exports = {router}는 오류남
module.exports = router
<userRouter.js>
const express = require('express');
const userController = require("../controllers/userController")
const router = express.Router() // express 자체 메서드 사용할 수 있음
// localhost:8000/users/signup으로 들어오면 아래로 연결됨
router.post('/signup', userController.createUser)
// module.exports = {router}는 오류남
module.exports = router
4) controllers
<userController.js>
signUp() 함수에서 유저 요청 처리 완료 후 userService.js의 signUp() 함수 호출
const userService = require("../services/userService")
const createUser = async (req, res) => {
const { username, password } = req.body;
const hasKey = { username: false, password : false };
const requiredKey = Object.keys(hasKey);
Object.entries(req.body).forEach((keyValue) => {
const [ key, value ] = keyValue
if (requiredKey.includes(key) && value){
hasKey[key] = true;
}
})
const hasKeyArray = Object.entries(hasKey);
for (let i = 0; i < hasKeyArray.length; i++){
const [key, value] = hasKeyArray[i]
if (!value){
res.status(400).json({message: `키(${key})가 존재하지 않습니다.`})
}
}
const user = await userService.createUser(username, password)
res.status(201).json({message: "userCreated"})
}
module.exports = {
createUser
}
5) services
<userService.js>
signUp() 함수에서 비즈니스 로직 처리 완료 후 userDao.js의 signUp()함수 호출
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const userDao = require("../models/userDao")
// res, req가 아니라 username, password를 인자로 받음
const createUser = async (username, password) => {
const salt = bcrypt.genSaltSync(12);
const hashedPw = bcrypt.hashSync(password, salt)
const user = await userDao.createUser(username, hashedPw)
return user
}
module.exports = { createUser }
6) models
<userDao.js 파일>
DB 관련 작업 처리
const { DataSource } = require('typeorm');
const myDataSource = new DataSource({
type: process.env.TYPEORM_CONNECTION,
host: process.env.TYPEORM_HOST,
port: process.env.TYPEORM_PORT,
username: process.env.TYPEORM_USERNAME,
password: process.env.TYPEORM_PASSWORD,
database: process.env.TYPEORM_DATABASE
})
myDataSource.initialize()
.then(() => {
console.log("Data Source has been initialized")
})
.catch(() => {
console.log("Database initiate fail")
})
const createUser = async (username, hashedPw) => {
const user = await myDataSource.query(
`INSERT INTO users (username, password) VALUES (?, ?)`,
[username, hashedPw]
)
console.log(`user : ${user}, username : ${user.username}, password : ${hashedPw}`)
// user : [object Object], username : garfield, password : $2a$12$G7f2kN2mWZmP.ycR0PTsEuVOFhTE/eL9NlFoTFQd1GzXS2Ex9fG7y
return user
}
module.exports = { createUser }
출처
[JUSTCODE] Layered pattern
'웹 프로그래밍 > Javascript' 카테고리의 다른 글
| 자바스크립트의 this (0) | 2022.10.17 |
|---|---|
| jest로 unit test하기 (0) | 2022.10.02 |
| Node.js에서 인증, 인가 진행하기(bcrypt, jwt) (0) | 2022.09.29 |
| Middleware (0) | 2022.09.28 |
| 에러/오류 핸들링 (0) | 2022.09.28 |