될때까지

((기업협업4)) router에 때려적었던 지저분한 스파게티 코드 분리하기 본문

프로젝트/wecode : 기업협업

((기업협업4)) router에 때려적었던 지저분한 스파게티 코드 분리하기

랖니 2022. 8. 20. 00:53
728x90

현재 내 엉망진창 코드의 상황은 아래와 같다. 

localhost:4000/users로 들어온 경우 index.js에서 적어준 것처럼 routes폴더의 userRouter.js로 보냈고,

// index.js

const express = require('express');
const cors = require('cors');
const fs = require('fs');
const path = require('path');

const app = express();

app.use(express.json());
app.use(express.urlencoded({extended:true}));
app.use(cors());

app.get('/', (req,res) => {
    res.send('hello node')
})

app.use('/users', require('./routes/userRouter'))

const port = 4000;
app.listen(port, () => {
    console.log('서버켜짐')
})

 

클라이언트로부터 들어온 데이터 확인, 실제 로직처리, DB접근 등 모든 처리를 router/router.userRouter.js에서 다 처리하고 있다.

const router = require("express").Router();
const db = require("../config/mysql");
const userController = require("../controllers/userController");

// 유저 등록하기
router.post("/", async (req, res, next) => {
  try {
    if (!req.body.username || !req.body.password) {
        throw {status:400, message:"KEY_ERROR"}
    }
    const sql = `
        insert into users (username, password) values ('${req.body.username}', '${req.body.password}');
        `;
    await db.query(sql);
    return res.status(200).json({ message: "success" });
  } catch (e) {
    return res.status(e.status || 500).json({message: e.message||"SERVER_ERROR"})
}
});

// 전제 유저 조회
router.get("/", async (req, res, next) => {
  try {
    const sql = "select * from users";
    const result = await db.query(sql);
    return res.status(200).json({ message: "success", result: result[0] });
  } catch (e) {
    return res.status(500).json({message:"SERVER_ERROR"})
  }
});

// 특정 유저 정보 가져오기
router.get("/:userId", async (req, res, next) => {
  try {
    const userId = req.params.userId;
    const sql = `select * from users where id=${userId}`;
    const [rows, fields] = await db.query(sql);
    if (rows.length) {
      return res.status(200).json({ message: "success", result: rows });
    } else {
        const err = new Error("USER_NOT_FOUND");
        err.status = 404
        throw err
    }
  } catch (e) {
    return res.status(e.status || 500).json({message: e.message || "SERVER_ERROR"})
}
});

// 특정 유저 정보 수정하기
router.post("/:userId", async (req, res, next) => {
  try {
    const check_sql = `select * from users where id=${req.params.userId}`;
    const [row, field] = await db.query(check_sql);
    console.log(row);
    if (row.length) {
      const sql = `UPDATE users SET username = '${req.body.username}', password='${req.body.password}' WHERE id = '${req.params.userId}'`;
      await db.query(sql);
      return res.status(200).json({ message: "updated" });
    } else {
        const err = new Error("USER_NOT_FOUND");
        err.status = 404
        throw err
    }
  } catch (e) {
    return res.status(e.status || 500).json({message : e.message || "SERVER_ERROR"})
}
});

// 특정 유저 삭제하기
router.delete("/:userId", async (req, res, next) => {
  try {
    const sql = `select * from users where id = ${req.params.userId};`;
    const [rows, fields] = await db.query(sql);
    if (rows.length) {
      const sql2 = `delete FROM users where id=${req.params.userId};`;
      await db.query(sql2);
      return res.status(200).json({ mesaage: "deleted" });
    } else {
      throw {status: 404, message:"USER_NOT_FOUND"};
    }
  } catch (e) {
    return res.status(e.status || 500).json({message : e.message || "SERVER_ERROR"})
  }
});

module.exports = router;

 

코드를 위 처럼 작성해도 되지만, 해당 코드는 삐 ---- 

  1. 가독성이 떨어짐
  2. 확장성이 떨어짐
  3. 재사용성이 떨어짐
  4. 유지보수가 힘듦
  5. 테스트 코드 작성이 어려움

과 같은 안좋은점만 가진 비효율적인 작성법이다. 

 

파이썬 장고는 MVT 패턴에 맞춰 코드를 작성했다.

Model/View/Template이 각각 데이터베이스/기능(로직)/화면만 담당할 수 있게끔 코드를 분리해서 작성했다지?

 

노드는 MVC 패턴이며 

Model/View/Controller가 각각 데이터베이스/화면/기능(로직)을 담당하고 있다.

여기에 나아가 노드는 장고처럼 다 생성이 된 게 아니라서 직접 다 만들어줘야한다 url conf같은 파일도! 이는 Router에서 담당한다. 

Model은 DB에 접근하고 해당 데이터를 받아오는 부분을 담당하고

View는 화면단

Controller는 어댑터 역할을 하고

여기에 추가로 Service라는 아이가 등장한다. 서비스에서 실제 비즈니스 로직을 담당한다고 한다.

뭔말이야

 

다시 정리하자면, 나는 노드는 아래와 같이 움직인다고 이해했다.

app -> Router -> Controller -> Service -> Model

(먼저 현재 나는 index.js라고 파일명을 정해놨는데, app.js를 명시적으로 많이 쓴다고하니 app.js로 바꿨다.)

 

1. app

app.js : express를 실행하면 Application이 반환되고 해당 Application은 use, get, listen같은 메소드를 가지고 있다.

// app.js

const express = require('express');
const cors    = require('cors');
const fs      = require('fs');
const path    = require('path');

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

const app = express();

app.use(express.json());
app.use(express.urlencoded({extended:true}));
app.use(cors());

app.use(routes);     // --> app.use()에 첫번째 인자인 path를 전달하지않으면 기본값으로 /가 정해진다.
                     // 즉 app.use(routes)라는 코드는 모든 경로로 들어오면 일단 routes로 보낸다는 뜻

const port = 4000;
app.listen(port, () => {
    console.log('서버켜짐')
})

 

 

2. Router

요청이 localhost:4000/users로 들어왔다고 치자. 그럼 app.js에서 작성한 코드에 따라 routes로 이동한다. 그냥 routes로 이동하면 기본값인 index.js로 향하게 된다.

// routes > index.js

const router     = require("express").Router();
const userRouter = require("./userRouter");

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

module.exports = router;

여기서 2차로 어디로 가야할 지 안내를 해준다. 엔드포인트가 /users로 들어왔다면 userRouter로 가~ 라고 작성되어있다. userRouter는 어떻게 작성했니?

// routes > userRouter.js

const router         = require("express").Router();
const userController = require("../controllers/userController");

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

module.exports = {
    router
}

/users로 오면 이제 그 뒤의 경로에 따라 다른 컨트롤러로 보내고 있다. /users/signup이면 컨트롤러의 signUp으로 보내고

/user/signin으로 오면 컨트롤러의 signIn으로 보낸다.

회원가입하는 /users/signup으로 가보자. 그럼 userController의 signUp으로 또 이동한다.

 

3. Controller

// controllers > userController.js

const userService = require("../services/userService");

const signUp = async (req, res) => {
    try {
        const { username, password } = req.body;

        if ( !username || !password ) {
            throw { status : 400, message : "KEY_ERROR"};
        }

        await userService.signUp (username, password);
        return res.status(201).json({ message : "SIGN_UP_SUCCESS" });

    } catch (e) {
        console.log(e);
        return res.status( e.status || 500 ).json({ message : e.message || "SERVER_ERROR" });
    }
};

module.exports = {
    signUp
}

컨트롤러는 엔드포인트에 해당하는 함수 로직을 담당한다.

음.. 아직까지 정확히 이해는 못했지만, 클라이언트로부터 들어온 요청의 데이터를 판별하여 서비스로 보내기에 알맞은 데이터인가 검증하는 작업까지만 담당한다고 이해를 했다.

그래서 코드를 보면 req 요청에 담긴 데이터들의 유무만 확인하게끔 작성했다.

해당 판별이 끝나면 그제서야 서비스로 요청을 보낸다.

 

4. Service

// services > userService.js

const userDao = require("../models/userDao");

const signUp = async (username, password) => {
    const pwValidation = new RegExp(
        '^(?=.*[A-Za-z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{8,20})'
    );

    if (!pwValidation.test(password)) {
        const err  = new Error("PASSWORD_IS_NOT_VALID");
        err.status = 400;
        throw err;
    }

    await userDao.createUser(
        username,
        password
    );
}

module.exports = {
    signUp
}

서비스는 컨트롤러에서 넘겨받은 인자로 다양한 로직들을 처리한다. 그래서 나도 비밀번호의 유효성 검사하는 로직을 넣어줬고 비밀번호 유효성 검사에 테스트하면 그제서야 모델로 입력받은 username과 password를 넘겨줬다.

 

5. model

// models > userDao.js

const db = require("../config/mysql");

const createUser = async (username, password) => {
    try {
        const sql = 
        `
        INSERT INTO 
            users 
            (username, password) 
        VALUES 
            ('${username}', '${password}');
        `;
        await db.query(sql);

    } catch (e) {
        const error  = new Error("INVALID_DATA_INPUT");
        error.status = 500;
        throw error;
    }
}

module.exports = {
    createUser
}

아직 DAO가 무엇인지 공부는 못했다. 하지만 데이터베이스에 접근하기 위한 sql문 작성같은 쿼리들을 이 파일에서 작성한다.

 

이렇게 MVC패턴 그리고 서비스?에 맞게 코드들을 분리했다.  맞겠지... 이게....???

728x90