npm install mysql2 --save
npm install dotenv --save
# .env
DATABASE_USER="username"
DATABASE_PASSWORD="password"
DATABASE_NAME="dbname"
DATABASE_HOST="localhost"
const mysql = require('mysql2/promise');
const dotenv = require('dotenv');
dotenv.config();
const conn = mysql.createPool({ //풀 생성
host: process.env.DATABASE_HOST,
user: process.env.DATABASE_USER,
password: process.env.DATABASE_PASSWORD,
database: process.env.DATABASE_NAME,
port: 3306,
waitForConnections: true, // 풀에 있는 연결이 모두 사용중일 때 대기
connectionLimit: 10, //풀에 넣을 수 있는 최대 연결 수
maxIdle: 10, // 최대 유휴 커넥션, default는 connectionLimit와 동일
idleTimeout: 60000, // 유휴 커넥션 timeout(ms), default는 60000
queueLimit: 0, //대기열에 넣을 수 있는 최대 요청 수
enableKeepAlive: true, //TCP 연결에 keep-alive를 넣을지 결정
keepAliveInitialDelay: 0 //keepAlive 패킷을 처음으로 보낼 때까지의 지연 시간
});
const mysql = require('mysql2/promise');
const dotenv = require('dotenv');
dotenv.config();
const conn = mysql.createPool({ //풀 생성
...
});
const getConnection = async () => { //connection 생성
try {
const connection = await conn.getConnection();
return connection;
} catch (error) {
console.error(`connection error : ${error.message}`);
return null;
}
};
const result = async () => {
const connection = await getConnection();
try {
await connection.beginTransaction(); //트랜잭션 begin
const query = `SELECT * FROM USER WHERE name = ? AND email = ?`;
const data = ['potato','potato@potato.com'];
const result = await connection.query(query, data);
await connection.commit(); //commit
return result;
} catch (err) {
console.log("rollback connection");
await connection.rollback(); // rollback
console.error("db error : ", err);
} finally {
console.log("release connection");
connection.release(); //connection release
}
}
... 이렇게 하면 기본은 끝나지만
Java, SpringBoot 를 사용했던 개발자라면 어딘가 불편할 것이다.
Spring 진영에는 AOP활용으로 @Transactional
을 통해 트랜잭션 로직과 비즈니스 로직을 분리 할 수 있었지만
nodejs에서는 특정 ORM을 사용하지 않는 이상 트랜잭션 로직과 비즈니스 로직을 분리하는 방법을 찾을 수 없었다.
그래서 최대한 분리를 해본 결과 아래와 같은 결과가 나왔다.
dbConfig.js
const mysql = require('mysql2/promise');
const dotenv = require('dotenv');
dotenv.config();
const conn = mysql.createPool({ //풀 생성
host: process.env.DATABASE_HOST,
user: process.env.DATABASE_USER,
password: process.env.DATABASE_PASSWORD,
database: process.env.DATABASE_NAME,
port: 3306,
...
});
const getConnection = async () => {
try {
const connection = await conn.getConnection();
return connection;
} catch (error) {
console.error(`connection error : ${error.message}`);
return null;
}
};
exports.transaction = async (logic, req) => {
const connection = await getConnection();
try {
await connection.beginTransaction();
const result = await logic(connection, req);
await connection.commit();
return result;
} catch (err) {
console.log("rollback connection");
await connection.rollback();
console.error("db error : ", err);
} finally {
console.log("release connection");
connection.release();
}
}
repository.js
exports.findAllByAccount = async (conn) => {
try {
const query = `SELECT * FROM serviceBilling ORDER BY start_at ASC`;
const [result] = await conn.query(query);
return result;
} catch (err) {
console.log("db err : ", err);
throw err;
}
}
service.js
const repository = require('../repository/repository');
exports.getUserData = async (conn, userId) => {
return await repository.findAllByAccount(conn);
}
controller.js
const service = require('../service/service');
const { tx } = require('../db/dbConfig');
exports.getServiceBillings = async (req, res) => {
const result = await tx(service.findBillingsByAccount);
return res.status(200).send(result);
}
최대한 분리를 해본 결과 계층간 분리는 되었으나 파라미터로 connection을 넘겨줘야했다.
이후에 알게 된 사실인데 TypeORM을 활용하면 분리가 된다는 것을 알게 되었다.
스프링을 주로 사용하다가 Node.js를 사용하면서 트랜잭션과 비즈니스 로직 분리에 어려움을 겪다보니 스프링이 얼마나 관심사 분리를 편리하게 제공했는지에 대해 알게 되었다. (물론 Node.js를 찍먹한 수준이라 더 공부해야 알겠지만...)
Node.js는 빠른 개발이 가능하지만 많은 사람들과 개발할 때는 조금은 불편할 수 있을것 같다.