[Node Express] 서버에 3 Layer Architecture 적용하기

mog·2020년 9월 21일
10

🍔 Intro.

아래 코드는 3 Layer Architecture를 적용하지 않고 모두 route 폴더에 작성한 예시입니다. 이렇게 작성하면 기능이 복잡해질 때, 한 파일의 코드가 너무 길어지고, 가독성도 떨어집니다. 또, layer나 모듈로 분리하지 않아서 여러 파일에 공통된 코드 (boilerplate code)가 존재합니다. 이런 문제를 해결하기 위해서 3 Layer Architecture를 도입하고자 했습니다.

router.get('/:banner_id', jwt.graphql.isLoggedIn, async (req, res) => {
    try {
        ///////////// 1. client에서 넘어오는 데이터 처리
        const { banner_id } = req.params; 
        
        ///////////// 2. DB에서 원하는 데이터 조회
        var transaction = await sequelize.transaction();
        const banners = await Banner.findOne({
            where: {
                id: banner_id
            },
            include: [
                {
                    model: Banner_sub_img,
                    order: [
                        ['order', 'ASC']
                    ]
                }
            ],
            transaction
        });
        await transaction.commit();

        ///////////// 3. client에 response 전송
        res.status(200).json(response.success(resMessage.READ_SUCCESS, banners));
    } catch (err) {
        error_handling.normal(err, res, transaction);
    }
});

🍕 3 layer architecture 란?.

3 layer architecture는 비즈니스 로직을 분리하는 것을 목적으로 하며 Controller, Service Layer , Data Access Layer 라는 세개의 층으로 나뉩니다. 간단히 설명하면 controller는 client와의 통신에서 필요한 req, res를 다루는 부분이고, service layer은 business logic을 Data Access Layer은 디비와의 직접적인 통신을 다룹니다. 이때, business logic은 client와 Data Acees Layer 사이에서 데이터를 조종하는 작업입니다.
이 구조의 가장큰 장점은 확장성입니다. 레이어 별로 분리하면 언제든지 필요에 따라 각각 독립적으로 크기를 조정하거나 수정할 수 있다는 것입니다.

✅ 다른 계층에 영향을 주지 않고 특정 계층만 수정, 확장할 수 있습니다.

🥯 controller

  1. 컨트롤러는 들어오는 클라이언트 요청을 받고 서비스에 전달한다.
  2. 서비스에서 작업을 마친 데이터를 받아 클라이언트에게 응답한다.

Controller에는 데이터를 가공하는 등의 비즈니스 로직을 추가하면 안된다.

route.post('/', 
  validators.userSignup, // this middleware take care of validation
  async (req, res, next) => {
    // The actual responsability of the route layer.
    const userDTO = req.body;

    // Call to service layer.
    const { user, company } = await UserService.Signup(userDTO);

    // Return a response to client.
    return res.json({ user, company });
});
module.exports = {
    readAll: async (req, res) => {
        CityService.readAll()
            .then(({
                    json
                }) =>
                res.send(json)
            ).catch(err => {
                res.send(err);
            });
    }
}

🧀 service

서비스 계층은 나머지 애플리케이션에서 모든 비즈니스 로직을 캡슐화하고 추상화합니다.

Service Layer Sould.

  • 비즈니스 로직 포함
  • 데이터 액세스 계층을 활용하여 데이터베이스와 상호 작용
  • controller 계층에 전달할 데이터 리턴

Service Layer Sould Not.

  • req , res 활용
  • 클라이언트에 대한 응답 처리
  • 데이터베이스와 직접 상호 작용
module.exports = {
    readAll: () => {
        return new Promise(async (resolve, reject) => {
            const city = await City.findAll({});
            if(city.length == 0) {
                resolve({
                    json: utils.successFalse(sc.NO_CONTENT, rm.CITY_EMPTY)
                });
                return;
            }
            if (!city) {
                resolve({
                    json: utils.successFalse(sc.INTERNAL_SERVER_ERROR, rm.CITY_READ_ALL_FAIL)
                });
                return;
            }
            resolve({
                json: utils.successTrue(sc.SUCCESS, rm.CITY_READ_ALL_SUCCESS, city)
            });
        });
    }
}

🥫 data Access Layer

  • 데이터 액세스 계층은 쿼리를 수행하여 데이터베이스와 상호 작용합니다. 제가 사용하고 있는 Sequelize는 Data Access Layer의 역할의 일부를 대체해줍니다.
  • sequelize를 사용하지 않으면 아래와 같이 data Access Layer를 담당하는 파일에 쿼리문을 모아서 필요할 때 service 계층에서 호출해서 사용합니다.
exports.getBoard = 'select board_id, title, content from board where board_id = ?'
exports.getBoards = 'select board_id, title from board limit ?, ?'
exports.insertBoard = 'insert into board set ?'
exports.updateBoard = 'update board set title = ?, content = ?, upd_dt = now() where board_id = ?'
exports.deleteBoard = 'delete from board where board_id = ?'
exports.getComments = 'select board_id, title, content from board where board_id = ?'
exports.insertComment = 'insert into board set ?'
exports.plusCommentCnt = 'update board set comment_cnt = comment_cnt + 1 where board_id = ?'
exports.updateComment = 'update board_comment set content = ?, upd_dt = now() where comment_id = ?'
exports.deleteComment = 'select board_id, title, content from board where board_id = ?'
exports.minusCommentCnt = 'update board set comment_cnt = comment_cnt - 1 where board_id = ?'

1개의 댓글

comment-user-thumbnail
2022년 3월 19일

일목요연한 설명 👍

답글 달기