express로 코드를 짜서 깃에 올리기 전에 3계층 레이어를 적용하면 좋다는 말에 여러 글을 보고 정리한 글이다.
1. 3 layer architecture란?
3 layer architecture는 비즈니스 로직을 분리하는 것을 목적으로 한다.
Controller, Service Layer , Data Access Layer 세개의 층으로 나뉜다.
- Controller : client와의 통신에서 필요한 req, res를 다룬다.
- Service layer : business logic을 Data Access Layer은 DB와의 직접적인 통신을 한다.
- Business logic : client와 Data Acees Layer 사이에서 데이터를 관리한다.
2. 3 Layer Architecture을 적용하는 이유
관심사 분리 원칙을 위해 API Route와 비즈니스 로직을 분리하고, 비즈니스 로직과 Data Access Layer를 분리한다. express.js controllers에 비즈니스 로직을 작성해도 되지만 결국 스파게티 소스가 될 수 밖에 없다. 유닛 테스트를 작성하다 보면 수많은 express.js의 req와 res 오브젝트를 다루기 때문이다.
3 Layer Architecture를 적용하지 않고, 모든 코드를 작성한다고 가정한다. 만약 여기서 기능이 복잡해질 경우, 한 파일의 코드가 너무 길어지고 가독성도 떨어집니다. 또한, layer나 모듈로 분리하지 않아서 여러 파일에 공통된 코드가 존재하게 된다. 이런 문제를 해결하기 위해서 3 Layer Architecture를 도입했다.
→ 이 구조의 가장 큰 장점은 확장성이다. 레이어별로 분리하면 언제든지 필요에 따라 각각 독립적으로 크기를 조정하거나 수정할 수 있다.
💡 다른 계층에 영향을 주지 않고 특정 계층만 수정, 확장할 수 있다.
3. 각 계층의 특성
1) Controller
사용자의 request를 분석한 후 알맞은 서비스로 요청을 전달한 다음 서비스의 결과를 다시 response하는 계층이다. Routing이 이뤄지는 계층이다.
- 컨트롤러는 들어오는 클라이언트 요청을 받고 서비스에 전달한다.
- 서비스에서 작업을 마친 데이터를 받아 클라이언트에게 응답한다.
Controller에는 데이터를 가공하는 등의 비즈니스 로직을 추가하면 안된다.
2) service
서비스 계층은 나머지 애플리케이션에서 모든 비즈니스 로직을 캡슐화하고 추상화한다.
🙆🏻♀️ 서비스 계층에 포함되는 것
- 비즈니스 로직 포함
- 데이터 액세스 계층을 활용하여 데이터베이스와 상호 작용
- controller 계층에 전달할 데이터 리턴
🤦🏻♀️ 서비스 계층에 포함되지 않는 것
- req , res 활용
- 클라이언트에 대한 응답 처리
- 데이터베이스와 직접 상호 작용
Controller로 부터 전달된 요청에 로직을 적용하는 계층이다.
3) data Access Layer
- 데이터 액세스 계층은 쿼리를 수행하여 데이터베이스와 상호 작용한다. Sequelize는 Data Access Layer의 역할의 일부를 대체해준다.
- Service 계층에서 데이터베이스 접근이 필요한 경우가 발생한다. 이에 대한 데이터 코드가 작성되는 계층이다. 로그인 서비스에서 회원의 정보가 데이터베이스에 존재하는지 확인해야 하고 존재 하게 되면 서비스 계층에 존재한다고 응답을 하게 된다.
4. Express에서 3Layer Architecture을 적용하기
1) Router
app.post('/orders', async function (req, res) {
try {
// Code for handling the '/orders' route
// ...
} catch (error) {
console.log(error);
res.status(500).send('An error occurred');
}
});
이 계층은 '/orders' 끝점에 대한 HTTP POST 요청을 처리한다. 요청을 받고 응답을 보낸다. 실행 중에 발생할 수 있는 예외에 대한 오류 처리가 포함되게 된다.
2) Service
async function handleOrderRequest(req, res) {
try {
const targetDate = req.body.targetDate;
const locationCode = req.body.locationCode;
let results1;
if (targetDate) {
const query1 = `SELECT * FROM orderView WHERE targetDate = ? AND locationCode LIKE CONCAT("%", ?, "%")`;
results1 = await executeQuery(query1, [targetDate, locationCode]);
} else {
const query1 = `SELECT * FROM orderView WHERE locationCode LIKE CONCAT("%", ?, "%")`;
results1 = await executeQuery(query1, [locationCode]);
}
if (results1.length > 0) {
// Code for processing and transforming the results1 data
} else {
res.send([]);
}
} catch (error) {
console.log(error);
res.status(500).send('An error occurred');
}
}
이 계층에는 '/orders' 경로를 처리하기 위한 비즈니스 논리가 포함되어 있다. 요청 데이터를 처리하고, 데이터베이스 쿼리를 실행하고, 데이터 변환을 수행하고, 응답을 준비합니다. 여기에는 데이터를 가져오고 처리하기 위한 여러 단계와 SQL 쿼리가 포함된다.
2-1) model
내 경우에 Service부분의 코드가 매우 길었다. 그래서 쿼리문도 몇개가 존재했다. 이를 줄일 수 있으면 좋지만, 줄이는 게 안된다면 model부분으로 따로 코드를 놔둔다. 그리고 메인 코드는 service에 넣고, 나머지 코드는 model부분으로 놔두어서 분리를 해놓는 것이 매우 간편하다.
const { executeQuery } = require("./dataAccess");
async function handleTimeBeltCodes(timeBeltCodesArray, selection, cnt) {
for (const timeBeltCode of timeBeltCodesArray) {
const query2 = `SELECT * FROM timeBelt WHERE timeBeltCode = ? AND isValid = "Y"`;
const results2 = await executeQuery(query2, [timeBeltCode]);
if (results2.length > 0) {
const name = results2[0].name;
for (const selectionItem of selection) {
if (selectionItem.timeBeltCode === timeBeltCode) {
selectionItem.timeBeltCode = name;
}
}
}
}
}
module.exports = { handleTimeBeltCodes, handleLocationCodes, handleStoreNos, handleOrderStates };
3) Data Access
function executeQuery(query, params) {
return new Promise((resolve, reject) => {
connection.query(query, params, (error, results, fields) => {
if (error) {
reject(error);
} else {
resolve(results);
}
});
});
}
이 계층에는 **executeQuery**SQL 쿼리를 실행하고 결과를 반환하는 기능이 포함되어 있습니다. Promise 기반 접근 방식을 사용하여 데이터베이스 쿼리의 비동기 특성을 처리합니다.
👇🏻 참고
https://www.softwareontheroad.com/ideal-nodejs-project-structure/#architecture
https://node-js.tistory.com/22
https://velog.io/@ju_h2/Node-express-서버에-3-Layer-Architecture-적용하기