sqliteDB

lsw·2021년 8월 31일
0

서버

목록 보기
6/6
post-thumbnail

1. sqlite DB

기존 데이터를 json형태로 service로직과 함께 저장하였다면 이번엔 파일형식 데이터베이스인 sqlite를 이용해 데이터베이스 관리를 진행할 예정

1.1. sequelize

시퀄라이즈는 객체를 통해 데이터베이스에 대한 C.R.U.D 액세스 권한을 가질 수 있도록 하는 모듈이다.

ORM : Object - Relational - Mapping
시퀄라이즈 객체를 데이터베이스 테이블과 매핑, 객체 매서드를 이용해 데이터베이스를 관리할 수 있는 방식을 의미한다.

// terminal
npm i sequelize

// source code

// Sequelize 클래스
const Sequelize = require('sequelize');

// sequelize 객체 : 생성자 이용
const sequelize = new Sequelize(database: string, username: string, password: string, options: object)

/* constructor.options
-options.host : 데이터베이스 호스트
-options.port : 데이터베이스 포트
-options.username : 데이터베이스의 인증된(authenticated) 사용자
.
.
.
-options.dialect : 연결하려는 데이터베이스. sqlite || mysql || mssql
-options.storage : 파일동작 방식인 sqlite에서만 사용함. 데이터가 저장될 파일생성 위치를 의미함
(default=memory)
.
.
.
-options.logging : 로깅을 찍을지 말지 결정. (default=true)
... 등등 많다. 
*/

doc 참조 : sequelize.org

  • 생성된 sequelize 인스턴스를 두 가지 곳에 사용할 예정이다.
  1. database 연동(동기화)
  2. database table 생성

1.1.2. database 동기화

// 상기 내용의 연장

// db.sync
sequelize.sync({ options... });

/* sync.options 
-force : 기존 테이블을 덮어 쓴다(totally new)
-alter : 기존 테이블을 정의된 모델(테이블)에 맞게 수정한다.
-match : 일종의 세이프티 옵션. 필터링 역할을 해준다.
*/

sequelize.sync({ force: true });
sequelize.sync({ alter: true });

// 파일명 끝이 _test로 끝나는 것만 필터링
sequelize.sync({ force: true, match: /_test$/ });

// db.drop
// 마찬가지 파일명 끝이 _test로 끝나는 것만 필터링
sequelize.drop({ match: /_test$/ });

sequelize.drop 결과

파일이 삭제되진 않고 비워졌다. empty!!!!!!!!!

1.1.3. database table 생성(model 정의)

// 상기 내용의 연장

// db.define 

const MyTable = sequelize.define(tablename, { field options... })

// options detail

const MyTable = sequelize.define(tableName, { 
		field1: {
			type: Sequelize.STINRG || INTEGER ...
			primaryKey: true || false(default)
			autoIncrement: true || false
			allowNull : true || false
      .
			.
			.
	}
		field2: ...
		field3: ...
		.
		.
		.
});

시퀄라이즈 클래스 인스턴스를 통해

  1. 데이터베이스 연결
  2. 데이터베이스 모델 정의

를 하였다. 한마디로 표현하면 '특정 스키마(형식)를 가진 파일형식의 데이터테이블에 접근권한을 갖게 됨'이 되겠다.

그렇다면 접근은 어떻게 해야할까?


2. sqlite C.R.U.D

handler.js

const models = require('./models');

// 생성 핸들러
exports.createSqlite = async function(tableName, data) {
    const result = await tableName.create(data);    
    return result;
};

// 겟 핸들러(단일 로우)
exports.getSqlite = async function(tableName, queryParams, atr ) {
    const result = await tableName.findOne({ where: queryParams, attributes: atr});
    return result;
};

// 겟 핸들러(다수 로우)
exports.getSqlites = async function(tableName, atr, lim, queryParams) {
    const result = await tableName.findAll({attributes: atr, limit: lim, where: queryParams });
    return result;
};

// 업데이트 핸들러
exports.updateSqlite = async function(tableName, newData, queryParams) {
    await tableName.update(newData, { where: queryParams });
    const result = await tableName.findOne({ where: queryParams }); 
    return result;
};

// 삭제 핸들러
exports.deleteSqlite = async function(tableName, queryParams) {
    await tableName.destroy({ where: queryParams });
    return true;
};

해당 핸들러를 이용해 우리가 정의한 데이터베이스 테이블에 접근하여 생성 / 삭제 / 추가 / 수정의 작업을 진행 할 수 있다. 이를 일전 만들어 둔 tdd의 service로직에 적용하여 보자.

service.js

// 기존 더미데이터를 가볍게 삭제해준다. 

const models = require('../../models');
// sqlite DB handler
const sqliteHandler = require('../../handler');

// service_method : getPlayer
exports.getPlayers =  async(req, res) => {
    if (req.query.limit === undefined) limit = players.length;
    else limit = parseInt(req.query.limit, 10); 
    if(Number.isNaN(limit)) return res.status(400).end();
    res.status(200);
    const attributes = ['club', 'name', ['age', 'how old is he?']]; // field값 제한
    const result = await sqliteHandler.getSqlites(models.Player, attributes, limit) 
    res.json(result).end();
}; 

// service_method : getMvpPlayer
exports.getMvpPlayer =  async (req, res) => {
    const final_mvp = parseInt(req.params.final_mvp, 10);
    console.log(final_mvp);
    if (Number.isNaN(final_mvp)) return res.status(400).end();
    const queryParams = {
        final_mvp
    }
    const attributes = ['club', 'name', ['final_mvp', `who's the best?`]];
    // const player = players.filter( player => player.final_mvp === mvp);
    const result = await sqliteHandler.getSqlite(models.Player, queryParams, attributes);
    res.json(result).end();
    };

// service_method : deletePlayer
exports.deleteMvpPlayer =  async (req, res) => {
    const mvp = parseInt(req.params.mvp, 10);
    if (Number.isNaN(mvp)) res.status(400).end();
    queryParams = {
        mvp 
    }
    const result = await sqliteHandler.deleteSqlite(models.Player, queryParams);
    res.status(204).json(result).end();
};

// service_method : createPlayer
exports.createPlayer =   async (req, res) => {
    const { club, name, age, salary, final_mvp } = req.body;
    if( !name ) {
        console.log('파라미터가 부족합니다');
        res.status(400).end();
    }
    const data = {
        club,
        name,
        age: Number(age),
        salary: Number(salary),
        final_mvp: Number(final_mvp)
    };
    const queryParams = {
        name
    }
    const NameConflictPlayer = await sqliteHandler.getSqlite(models.Player, queryParams);
    if (NameConflictPlayer) return res.status(409).end(); 
    const result = await sqliteHandler.createSqlite(models.Player, data);
    res.status(201);
    res.json(result).end();
};

// service_method : editPlayer
exports. editPlayer =   async (req, res) => {
    const { id } = req.params;
    const findQueryParams = {
        id
    }
    const { club, name, age, salary, final_mvp } = req.body;
    const nameQueryParams = {
        name
    }
    const data = {
        club,
        name,
        age,
        salary,
        final_mvp
    }
    const tableName = models.Player;
    if (typeof club != 'string' || !name) return res.status(400).end(); // 이름이 없거나 클럽명이 숫자인 경우
    const AlreadyExistPlayer = await sqliteHandler.getSqlite(tableName, findQueryParams); // key값에 해당하는 데이터 존재여부 파악
    if ( !AlreadyExistPlayer ) return res.status(404).end(); // 데이터 미존재
    const CheckNameConflict = await sqliteHandler.getSqlite(tableName, nameQueryParams); // key값에 해당하는 데이터 존재여부 파악
    if ( CheckNameConflict ) return res.status(409).end(); // 이름 이미 존재하는 경우
    
    const result = await sqliteHandler.updateSqlite(tableName, data, findQueryParams);
    console.log(result);
    res.json(result).end();
};

// 데이터베이스 테이블을 ORM으로 추상화한 것을 모델이라 하며 이 모델을 통해 각종 매서드들에 접근하는 것이다.
// 모델을 정의하고(sequelize.define()) -> 정의된 모델과 데이터베이스를 연동한다. (sequelize.sync())

2.1. 핸들러에 사용된 내장 함수

// create
definedModelName.create(data); 
// 테이블에 데이터 내용 로우 생성

// update
definedModelName.update(editData, { where: queryParams });
// 조건에 해당하는 로우들을 editData로 수정한다.

// delete
definedModelName.delete({ where: queryParams })

// get
definedModelName.findOne( options: { where: queryParams, attribute: attr})
// 조건에 해당하는 하나의 로우의 attribute조건에 해당하는 필드만 겟한다.

definedModelName.findAll( options: { attribute: attr, limit: lim, where: queryParams})
// 조건에 해당하는 하나 이상의 로우의 attribute조건에 해당하는 필드를, 데이터 수 가 최대limit개수 이하가 되게끔 겟한다.

tdd test code

.spec.js

const request = require('supertest');
const app = require('../../server');
const should = require('should')
const models = require('../../models');

 describe('GET : /players는', () => {
    before( () => models.sequelize.sync({}));
     describe('성공 시', () => {
         it('player list를 반환한다.', (done) => {
             request(app)
                .get('/players')
                .end((err, res) => {
                    if(err) throw err;                                
                    res.body.should.be.instanceOf(Array); 
                    done();               
                });
         });
         it('최대 limit갯수만큼 응답한다.', (done) => {
             request(app)
                .get('/players?limit=3')
                .end((err, res) => {
                    if (err) throw err;
                    res.body.should.have.lengthOf(3)
                    done();
                });
         });
     });
     describe('실패 시', () => {
         it('limit이 숫자형이아니면 400을 응답한다.', (done) => {
             request(app)
                .get('/players?limit=three')
                .expect(400, done);
                /* .end((err, res) => {
                    if (err) throw err; // expect에서 발생하는 error가 end매서드로 던져진 것. 따라서 throw처리를 하지 않으면 에러가 처리되지 않음.
                    done();
                });
                */
         });
     });
 });

 describe('GET : /players:mvp 는', () => {
     describe('성공 시', () => {
         it('mvp가 2인 객체를 반환한다.', (done) => {
             request(app)
                .get('/players/2')
                .end((err, res) => {
                    if(err) throw err;
                    res.body.should.have.property(`who's the best?`, 2);
                    done();
                });
         });
     });
 });

describe('DELETE : /players/:mvp는', () => {
    before( () => models.sequelize.sync({}));
    describe('성공 시', () => {
        it('상태코드 204를 반환한다.', (done) => {
            request(app)
               .delete('/players/6')
               .expect(204)
               .end((err, res) => {
                   done();
               });
        });
    });
    describe('실패 시', () => {
        it('mvp가 숫자가 아니면 상태코드 400을 반환한다.', async () => {
            await request(app)
               .delete('/players/four')
               .expect(400)
        });
    });
});

describe('POST : /players 는', () => {
    before( () => models.sequelize.sync({}));
    describe('성공 시', () => {
        let body;
        before(done => {
            request(app)
               .post('/players')
               .send({
                   club: 'pistons',
                   name: 'cunningham',
                   age: 19,
                   salary: 10,
                   final_mvp: 0
                })
               .expect(201)
               .end((err, res) => {
                   if (err) throw err;
                   body = res.body;
                   done();  
               });
        });
        it('입력한 이름을 반환한다.', () => {
            body.should.have.property('name', 'cunningham');
        });
        it('생성된 유저객체를 반환한다.', () => {
            body.should.be.instanceOf(Object); // 객체를 반환한다.
        });
    });
    describe('실패 시', () => {
        it('이름 누락 시 400을 반환한다.', done => {
            request(app)
               .post('/players')
               .send({
                   club: 'cleveland',
                   age: 33,
                   salary: 24,
                   final_mvp: 0
               })
               .expect(400)
               .end(done);
        });
        it('name이 중복일 경우 409를 반환한다.', done => {
            request(app)
               .post('/players')
               .send({
                   club: 'celtics',
                   name: 'durant',
                   age: 33,
                   salary: 33,
                   final_mvp: 0
                })
               .expect(409)
               .end(done);
        });
    });
});

describe.only('PUT : /players 은', () => {
    before( () => models.sequelize.sync({})); // sequelize와 동기화(return 으로 비동기 처리)
    describe('성공 시', () => {
        data1 = {
            club: 'jazz',
            name: 'ingles',
            age: 29,
            salary: 20,
            final_mvp:0,  
        }
        it('변경된 정보를 응답한다.', done => {
            request(app)
               .put('/players/1')
               .send(data1)
               .end((err, res) => {
                   if (err) throw err;                   
                   res.body.should.have.property('name', 'ingles');
                   done();
               });
        });
    });
    describe('실패 시', () => {
        data2 = {
            club: 3,
            name: 'Russ',
            age: 32,
            salary: 42,
            final_mvp: 0        
        }
        it('문자열이 아닌 club일 경우 400을 응답', done => {
            request(app)
               .put('/players/1')
               .send(data2)
               .expect(400)
               .end(done);
        });
        data3 = {
            club: 'lakers',
            age: 23,
            salary: 30,
            final_mvp: 0        
        }
        it('name이 없을 경우 400 응답', done => {
            request(app)
               .put('/players/1')
               .send(data3)
               .expect(400)
               .end(done);
        });
        data4 = {
            club: 'GSW',
            name: 'curry',
            age: 33,
            salary: 25,
            final_mvp: 0        
        }
        it('조건에 맞는 선수가 없을 경우 404 응답', done => {
            request(app)
               .put('/players/100')
               .send(data4)
               .expect(404)
               .end(done);
        });
        data5 = {
            club: 'lakers',
            name: 'doncic',
            age: 31,
            salary: 25,
            final_mvp: 0        
        }
        it('이름이 중복일 경우 409 응답', done => {
            request(app)
               .put('/players/2')
               .send(data4)
               .expect(409)
               .end(done);
        });
    });
});

테스트 코드의 내용은 앞선 포스트와 완전이 일치하니 참고하길 바람.

profile
미생 개발자

0개의 댓글