2022. 2. 13. 11:50ㆍReact, Javascript
지난 포스팅을 통해 Typescript를 활용하여 NodeJS와 Express 백엔드 서버 개발환경을 세팅하는 방법을 알아봤다.
이것을 베이스로 웹 프레임워크에서 많이 사용하는 MVC 패턴을 만드는 방법에 대해 알아보자.
MVC 패턴은 디자인 패턴 중 하나이다.
사실 주니어 시절에는 이걸 왜 쓰는지 잘 알지 못했다.
주니어 시절은 자신이 맡은 기능만을 구현하면 되기 때문에 서비스 전체 코드에 대한 전반적인 사항들을 알기 힘든게 가장 큰 이유였던 거 같다.
처음 유지보수를 위해 프로젝트에 투입되었을 때 Spring 프레임워크로 된 서비스 코드들을 접했다.
특정 기능들을 JSP 화면부터 따라가면서 DB 조회하는 부분까지 코드 분석을 하는데 많은 시간이 걸렸다.
그러면서 드는 생각은 왜 이렇게 코드들을 쪼개놨을까 하는 불만이였다.
한 기능을 담당하는 코드를 한곳에 두면 새로 투입되는 개발자들이 편할텐데 이런 작업이 왜 되어 있을까....
지금 생각하면 참 부끄럽다.
그 이후 많은 프로젝트들에 투입되며 복잡한 기능들을 구현하고 여러 개발자들과 협업하다 보니 잘 짜여진 디자인 패턴이 가지는 힘을 새삼 느끼게 되었다.
그래서 이제는 간단한 토이 프로젝트나 빠른 백엔드 서버 구현에도 MVC 패턴을 고수하는 편이다.
당장은 혼자 개발하지만 시간이 흘러 다시 소스코드를 봐야한다면 결국 미래의 나와 협업하는 것이 되기 때문이다.
잡설이 길었다.
Typescript 기반의 Node Express 백엔드 서버에 MVC 패턴을 적용해 보자.
1. MVC 패턴이란?
MVC 패턴이란 Model, View, Controller 의 약자이다.
어플리케이션을 구성할 때 각자 맡은 역할대로 소스코드를 구분하여 코딩하고 유지보수 및 신규기능 추가를 더 수월하게 하겠다는 의도이다.
자동차 공장을 생각해보자.
옛날에는 자동차 한대를 만드는 데 모든 부품을 만드는 사람들이 한곳에서 작업을 해 하나의 완성된 자동차를 만들었다.
하지만 이제는 효율화를 위해 엔진, 자동차 외부, 바퀴, 자동차 문 등으로 섹션을 나누어 작업한다.
이런 효율성을 IT로 가져왔다고 생각하면 편하다.
Model은 데이터에 관련된 행위들만 담당하고
View는 화면에 관련된 행위들만 담당하고
Controller는 사용자 요청에 대한 행위들만을 담당한다.
MVC는 지금처럼 Front-End 분야가 크기 전에 만들어진 개념이다.
따라서 모든 것을 서버단에서 처리하던 시절이기에 위 같은 구성을 만들었다.
지금은 백엔드를 구성하더라도 Rest API만을 주는 서버를 만들고 화면에 대한 것은 React나 Vue 등으로 따로 구성하는 것이 대부분이다.
그렇다면 MVC 패턴은 고대의 유물 같은 것이 아닐까?
결론적으로 그렇지는 않다.
개발자들은 실제 화면(View)가 백엔드에 없더라도 화면과 직접적으로 통신하는 부분을 View단으로 개념화 해서 MVC 패턴을 사용하고 있다.
이것은 프론트엔드 쪽에서도 마찬가지이다.
화면 내에서도 사용자의 요청을 받아드리는 부분이 있고 백엔드와 통신해서 데이터를 핸들링 해야할 부분이 있다.
실제 React에서도 MVC 패턴을 채용해 많이 개발한다.
그렇다면 Rest API를 주는 목적은 NodeJS+Express 서버는 MVC를 어떻게 구현해야 할까
2. NodeJS + Express MVC 구성 개념
Express에서는 Router라는 것을 제공하여 화면과 최전방에서 상호작용하는 것을 처리한다.
이것을 View단이라고 생각한다.
이후 화면에서 들어온 요청에 맞는 데이터 조회, 수정, 삭제 등의 작업을 지휘할 controller 부분을 따로 빼준다.
그리고 실제 DB에서 데이터를 핸들링 하는 행위를 하는 model 부분을 빼서 관리하면 된다.
이렇게 하면 필요한 로직들을 어느 모듈에 적용해 놔야 하는지 어느정도는 감이 생길 것이다.
예를 들어 DB를 연결하고 Pool을 생성해 쿼리 작업을 하는 쪽은 model에 있어야 한다.
화면에서 온 데이터들에 대한 validation 처리를 view, 즉 router에서 하는게 좋다.
3. 실제 구성
이 구성은 Typescript를 기반으로 하고 DB로는 Mysql을 사용했다.
Sequelize를 사용하는 것이 좋지만 토이프로젝트나 작은 규모의 플젝이라면 DB를 직접 핸들링하는 일이 많으므로 쿼리를 활용한 DB 작업을 하도록 하겠다.
어플리케이션의 admin기능 중 admin 유저들을 등록하고 admin 유저들을 조회하는 기능을 만들것이다.
프로젝트 폴더 구성은 다음과 같다.
폴더 네이밍은 그렇게 중요하지 않다.
MVC 패턴의 개념을 이해했고 해당 기능별로 모듈을 나눠서 구성하기만 하면 된다.
Controller : 말그대로 Controller를 담당하는 모듈들을 가지고 있다.
Data : 여기서 Model을 담당하는 모듈들을 가지고 있다.
Model : 혼동이 있을 수 있지만 여기는 Typescript를 사용하기 때문에 생기는 데이터의 타입들에 대한 정의 부분이다.
routes : View를 담당하는 부분이다.
util : 기타 자주 쓰이는 공통모듈들을 관리한다.
app.ts : 실제 중심이 되는 Main 모듈이다.
다시한번 언급하지만 폴더명은 필자가 개인적으로 작업하기 편하게 명명한 것들이다.
플젝에서는 플젝의 네이밍 규칙에 따르고 개인은 취향대로 바꿔도 전혀 상관없다.
app.ts 부터 살펴보자.
import express from "express";
import adminRouter from "./routes/admin";
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use("/admin", adminRouter);
app.listen(8000, () => {
console.log("**----------------------------------**");
console.log("==== Server is On...!!! ====");
console.log("**----------------------------------**");
});
매우 심플하다.
adminRouter에 요청을 위임하고 8000 port로 서버를 기동시켰다.
다음은 MVC 중 View에 해당하는 router인 /routes/admin.ts 모듈을 살펴보자.
import express, { Request, Response, NextFunction } from "express";
import * as adminController from "../controller/admin";
const router = express.Router();
router.get("/admin", adminController.getAdmin);
router.post("/admin", adminController.createAdmin);
export default router;
Router에서는 http 메소드에 따라 get으로 온 요청은 Controller의 조회쪽으로 post 요청은 생성 쪽으로 역할을 위임한다.
또한 여기서 @types/express-validator 등을 사용하여 화면에서 받은 데이터들의 유효성 검증을 미들웨어 형식으로 해주면 된다.
다음으로 /controller/admin.ts 이다.
import { Request, Response } from "express";
import { PostAdmin, GetAdmin } from "../model/admin";
import * as adminData from "../data/admin";
// 생성 후 insertId를 리턴하도록 설계
export async function createAdmin(req: Request, res: Response) {
const newAdminInfo: PostAdmin = req.body;
const insertId = await adminData.createAdmin(newAdminInfo);
res.status(201).json({ inserId: insertId });
}
//모든 admin user 들을 배열로 전송하도록 설계
export async function getAdmin(req: Request, res: Response) {
const adminInfo: Array<GetAdmin> = await adminData.getAdmin();
res.send(adminInfo);
}
리턴하거나 Model 쪽에 넘겨줘야할 데이터의 타입을 정의한 model들(폴더명)을 import해오고 데이터를 핸들링 하는 Model에 해당하는 data를 호출해 준다.
DB조회하는 부분으로 async 방식을 활용해 동기적으로 처리 될 수 있도록 구성한다.
다음은 MVC 중 Model에 해당하는 /data/admin.ts 이다.
import { db } from "./database";
import { PostAdmin, GetAdmin } from "../model/admin";
export async function createAdmin(newAdminInfo: PostAdmin): Promise<string> {
const {
adlvno,
adid,
adpw,
adname,
adress,
findpass_que,
findpass_ans,
email,
tel,
depart,
duty,
} = newAdminInfo;
const query: string =
"INSERT INTO tb_admin VALUES(2,?,?,?,?,?,?,?,?,?,?,?,NOW(),NOW())";
return db
.execute(query, [
adlvno,
adid,
adpw,
adname,
adress,
findpass_que,
findpass_ans,
email,
tel,
depart,
duty,
])
.then((result: any) => result[0].insertId);
}
export async function getAdmin(): Promise<Array<GetAdmin>> {
const query: string =
"SELECT ta.adlvno, ta.adid, ta.adname, ta.email, ta.depart, tal.lvname, ta.duty from tb_admin as ta, tb_admin_level as tal where ta.adlvno=tal.adlvno";
return db.execute(query).then((result: any) => result[0]);
}
DB에 테이블이 두개다. admin 유저를 관리하는 tb_admin, admin 등급을 관리하는 tb_admin_level
다음으로 DB 연결에 필요한 /data/database.ts 파일이다.
import mysql from "mysql2";
const pool = mysql.createPool({
host: process.env.DB_HOST, // 호스트 주소
user: process.env.DB_USER, // mysql user
password: process.env.DB_PASSWORD, // mysql password
database: process.env.DB_DATABASE, // mysql 데이터베이스
port: 3306,
});
export const db = pool.promise();
보안을 위해 .env 파일을 활용해 DB 정보들을 가져온다.
다음으로 type을 정의해둔 /model/admin.ts 이다.
export type PostAdmin = {
adlvno: number;
adid: string;
adpw: string;
adname: string;
adress: string;
findpass_que: string;
findpass_ans: string;
email: string;
tel: string;
depart: string;
duty: string;
};
export type GetAdmin = {
adid: string;
adname: string;
email: string;
depart: string;
duty: string;
lvname: string;
};
여기까지 NodeJS+Express에 Typescript로 MVC 패턴을 구성해 보았다.
디자인 패턴은 협업과 프로젝트 유지보수에 중요한 역할을 한다.
MVC 말고도 다양한 패턴이 존재하니 한번쯤 적용해 보는 것도 나쁘지 않을 것이다.
'React, Javascript' 카테고리의 다른 글
[React] Flux 패턴과 MVC 패턴 (feat. Redux) (1) | 2022.04.08 |
---|---|
Typescript로 Node+Express API 서버 개발환경 세팅 (1) | 2022.02.07 |
[React] 이미지 사용의 모든 것(feat. SVG, SVGO) (0) | 2022.01.14 |
[React-Typescript] form 지옥에서 벗어나기 (formik) (0) | 2021.12.30 |
[React] 상대경로 지옥에서 벗어나기 (craco) (1) | 2021.06.14 |