ORM 이란?
웹 어플리케이션을 제작할 때 3-tier로 아키텍처를 구성합니다. 클라이언트 ~ 서버 ~ 데이터베이스 3개의 tier로 구성된 아키텍처를 범용적으로 사용하는 이유는 사용자가 민감한 정보가 담긴 DB에 접근이 안되도록 하면서 각 tier에서 업무 분담을 하기 위해서 입니다.
디자인 패턴이란 무엇일까요? 소프트웨어 공학에서 프로그램을 설계하는 패턴을 의미합니다. 대표적으로 MVC 디자인 패턴을 사용합니다. Model, View, Controller로 구성된 소프트웨어 방법론입니다. Model과 Controller 사이를 연결하는 역할을 담당하는것이 바로 ORM(Object Relational Mapping) 입니다.
ORM을 이용하면 SQL문을 사용하지 않고 Model을 생성하고 DB에 접근해서 데이터를 읽어올 수 있습니다. 또한 Database의 Entity를 클래스 단위로 관리하고, 서버 언어를 그대로 사용하기 때문에 데이터를 편리하게 관리할 수 있습니다.
ORM은 다양한 종류가 있습니다. 그 중에서도 Node.js 환경에서 서버를 개발하신다면 Sequelize를 사용하시게 됩니다. Sequelize 모듈 내에서도 MySQL, Postgres, mariaDB, splite3, tediou 등 다양한 DB 프로그램을 지원하고 있습니다.
Sequelize 설치
ORM과 Sequelize의 개념을 간단하게 이해하고, 실제 프로젝트를 만들어서 어떻게 Model을 작성하고, controller로 접근해서 데이터를 가져오는지 간단하게 알아보도록 하겠습니다.
프로젝트를 생성합니다.
$ npm init
Sequelize를 포함한 프로젝트의 필요모듈을 설치합니다. mysql2는 DB 프로그램이 아닌 sequelize에서 사용하는 DB 모듈입니다. 이번 프로젝트에서는 MySQL을 사용합니다.
$ npm install express sequelize sequelize-cli mysql2
$ npm install -D nodemon
ORM을 사용해 프로젝트의 뼈대를 잡습니다. config, models, migrations, seeder 폴더가 생성됩니다. npx 명령문을 사용해서 전역에서 sequelize를 실행할 수 있습니다.
$ npx sequelize init
express 서버 작성
메인 서버 파일 (app.js)를 생성합니다.
$ touch app.js
서버에서는 sequelize 모듈을 가져오고 sequelize.sync() 메서드를 사용해서 DB를 실행합니다. force 옵션을 줄 수 있는데, true로 설정하면 model 폴더 내에 정의한 클래스들을 사용해서 서버가 재 실행될 때 마다 테이블을 재생성합니다. DB에 접근하기 위한 ID, PASSWORD 정보는 config/config.json 폴더에 저장되어 있습니다. development, test, production 3가지 모드를 지원하고 있고, 각각에 DB 정보를 입력해줍니다.
const express = require('express');
const path = require('path');
// sequelize import
const { sequelize } = require('./models');
const route = require('./routes')
const app = express();
// sequelize 실행
sequelize.sync({foce : false})
.then(()=>{
console.log("Database connected")
})
.catch((err)=>{
console.error(err);
})
app.set('port', process.env.PORT || 4999);
app.use('/', route);
app.listen(app.get('port'), ()=>{
console.log("Server is on ", app.get('port'));
})
models의 index.js 파일을 작성합니다. env 변수를 설정해서 현재 모드를 변경할 수 있습니다. config.json에 설정한 DB정보를 가지고 sequelize 객체를 생성한 후 db에 엔티티들을 연결합니다. sequelize 객체는 Model 클래스를 상속받은 클래스로 init 메서드로 테이블을 생성하고, associate 메서드로 테이블을 연결합니다.
const Sequelize = require('sequelize');
const Urls = require('./urls')
const env = process.env.NODE_ENV || 'development';
const config = require('../config/config')[env];
const db = {};
// sequelize 객체
const sequelize = new Sequelize(config.database, config.username, config.password, config);
db.sequelize = sequelize;
db.Urls = Urls;
Urls.init(sequelize);
Urls.associate(db);
module.exports = db;
여기서 작성할 테이블은 Users, Urls 테이블입니다. 스키마는 다음과 같습니다. 2개의 테이블은 1:N의 관계를 가지고 있습니다. JOIN을 사용해서 User의 정보를 가져올 수 있도록 관계를 구성합니다.
model 폴더 내에 urls.js / users.js 모델 파일을 생성합니다. init 메서드의 인수로 sequelize를 전달받고, Model 클래스의 init() 메서드 인수로 테이블을 정의한 객체데이터를 전달합니다. 이를 기초로 테이블이 생성됩니다. associate 메서드에는 테이블 관계를 표시합니다. Users의 id와 Urls의 owner는 1:N 관계이기 때문에 hasMany()와 belongsTo() 메서드를 사용합니다. 이제 Urls 테이블에 자동으로 owner 칼럼이 생성됩니다.
const Sequelize = require('sequelize');
module.exports = class Users extends Sequelize.Model{
static init(sequelize){
return super.init({
firstName : {
type : Sequelize.STRING(255),
allowNull : true,
defaultValue : null,
},
lastName:{
type : Sequelize.STRING(255),
allowNull : true,
defaultValue : null,
},
email:{
type : Sequelize.STRING(255),
allowNull : true,
defaultValue : null,
},
createdAt : {
type : Sequelize.DATE,
allowNull : true,
defaultValue : null,
},
updatedAt : {
type : Sequelize.DATE,
allowNull : true,
defaultValue : null,
}
},{
sequelize,
timestamps : false,
underscored : false,
modelName : 'Users',
tableName : 'users',
paranoid : false,
charset : 'utf8',
collate : 'utf8_general_ci',
})
}
static associate(db){
db.Users.hasMany(db.Urls, {
foreignKey: 'owner',
sourceKey: 'id',
});
}
}
생성된 테이블에 데이터를 넣기 위해서는 seeder 폴더내의 데이터 파일을 사용합니다.
$ npx sequelize-cli seed:generate --name users
seeder 파일은 크게 up메서드와 down 메서드로 구분됩니다. up 메서드에 bulkInsert 메서드를 사용해서 특정 테이블에 특정 데이터를 삽입합니다. public/data 에 저장된 데이터를 불러와 각 테이블에 데모 데이터들을 삽입해줍니다.
'use strict';
const data = require('../public/data/seeder_data')
const userData = require('../public/data/seeder_user_data')
module.exports = {
async up (queryInterface, Sequelize) {
/**
* Add seed commands here.
*
* Example:
* await queryInterface.bulkInsert('People', [{
* name: 'John Doe',
* isBetaMember: false
* }], {});
*/
await queryInterface.bulkInsert('users', userData, {} )
await queryInterface.bulkInsert('urls', data, {});
},
async down (queryInterface, Sequelize) {
/**
* Add commands to revert seed here.
*
* Example:
* await queryInterface.bulkDelete('People', null, {});
*/
}
};
seeder 데이터를 테이블에 삽입하는 명령어입니다.
$ npx sequelize-cli db:seed:all
마지막으로 라우터를 작성해주고, 2개의 테이블을 조인해 데이터를 검색합니다. JOIN을 사용하기 위해서는 include 속성을 사용합니다. 이제 서버를 작동하고 요청을 보내면 JOIN된 테이블의 데이터가 검색됩니다.
route/index.js
const express = require('express');
const Urls = require('../models/urls')
const Users = require('../models/users')
router = express.Router();
router.get('/', async (req, res)=>{
const data = await Users.findOne({
include : [{
model : Urls,
where : {owner : 1},
attributes : ['id', 'url', 'title']
}],
attributes : ['id', 'firstName']
})
res.json({data : data});
})
module.exports = router;
Reference
'Programming' 카테고리의 다른 글
[Node.js] PNG to webp Converter 구현하기 (0) | 2022.06.19 |
---|---|
[Security] CSRF란? (0) | 2022.06.14 |
[Docker] Node.js Dockerfile로 배포 하는 방법 (0) | 2022.06.12 |
[Docker] 도커 사용하는 이유? (0) | 2022.06.10 |
[Docker] Ubuntu 우분투 20.04 LTS 도커 설치 방법 (0) | 2022.06.10 |
댓글