아래 코드는 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는 비즈니스 로직을 분리하는 것을 목적으로 하며 Controller, Service Layer , Data Access Layer 라는 세개의 층으로 나뉩니다. 간단히 설명하면 controller는 client와의 통신에서 필요한 req, res를 다루는 부분이고, service layer은 business logic을 Data Access Layer은 디비와의 직접적인 통신을 다룹니다. 이때, business logic은 client와 Data Acees Layer 사이에서 데이터를 조종하는 작업입니다.
이 구조의 가장큰 장점은 확장성입니다. 레이어 별로 분리하면 언제든지 필요에 따라 각각 독립적으로 크기를 조정하거나 수정할 수 있다는 것입니다.
✅ 다른 계층에 영향을 주지 않고 특정 계층만 수정, 확장할 수 있습니다.
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 Layer Sould.
❌ 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)
});
});
}
}
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 = ?'
일목요연한 설명 👍