[Node.js Project] es6 Class Generator - 클래스 생성기 만들
개요
es6 형 클래스 생성을 위한 Node.js 모듈 생성 데이터베이스 테이블의 열 이름으로 나열된 열 이름으로 DTO 클래스 생성하는 경우 사용!
물론 이 클래스를 이용하지 않아도 될 경우가 좀더 JS 형식에 맞는 형태 일 수 있으나 필요하여 다음의 조건을 만족하는 클래스 생성기를 만들어 본다.
물론 JS 특성상 구지 클래스를 만들지 않아도 객체 자체를 사용해도 되는 경우가 대부분이다. 하지만! DB 를 다루는 서버 입장에서는 DTO, DAO, SERVICE, CONTROLLER 등으로 계층별로 다룰때 DTO 객체를 정의해서 정형 데이터베이스로 부터 데이터를 처리하는 경우가 많이 있었다.(물론 이 구조가 올바른지는…)
이때 형식이 결정되어 있는 DTO 객체 사용하는 경우에 한하여
이때 쓰이는 class 를 자동 생성
요구사항
Node.js 기반으로 js 스크립트에서 실행가능하며 require 또는 import 이용하여 로딩 할 수 있도록 함
함수 호출 하여 사용하도록 한다. 함수 호출 방식은 다음과 같다.
입력: 클래스 이름, 데이터베이스 테이블의 열이름 문자열
ex) 클래스명: Person,
필드명: ID, NAME, GENDER, REG_DATE
출력: 이 조건들을 이용하여 만든 클래스 파일
class Person extends Model {
constructor(object){
this._id = obejct[Person.FIELD_ID];
this._name = obejct[Person.FIELD_NAME];
this._gender = obejct[Person.FIELD_GENDER];
this._reg_date = obejct[Person.FIELD_REG_DATE];
}
/* STATIC FIELD NAME*/
static get FIELD_ID = 'ID';
static get FIELD_NAME = 'NAME';
static get FIELD_GENDER = 'GENDER';
static get FIELD_REG_DATE = 'REG_DATE';
/*getter & setter*/
get id() {
return id;
}
set id(id) {
this._id = _id;
}
get name() {
return this._name;
}
set name(name) {
this._name=_name;
}
get gender() {
return this._sex;
}
set gender(sex) {
this._sex = sex;
}
get red_date(reg_date) {
return this._reg_date;
}
set reg_date(reg_date) {
this._reg_date = reg_date;
}
}
구현
위 요구사항을 만족하기 위해 다음과 같이 구현한다.
폴더구조
템플릿
genDTO index.js
폴더 구조
폴더 구조는 다음과 같다.
genDTO : 최상위 폴더
├── index.js : 모듈 index.js
└── template : 템플릿 양식 저장
└── es6ClassType1-forRDB.txt : Relational Database DTO 생성을 위한
템플릿
템플릿은 %s 와 같은 형식 지정자로 구성하여 다음을 구성한다.
es6ClassType1-forRDB.txt
/* eslint-disable no-underscore-dangle,camelcase */
const Model = require('./Model');
const async = require('async');
class %s extends Model {
constructor(object) {
super(object);
if (object) {
%s
}
}// constructor()
/* STATIC FIELD NAMES */
%s
/* getter & setter */
%s
}// end of class
module.exports = %s;
|;|this._%s = object[%s.FIELD_%s];
|;|static get FIELD_%s() { return '%s'; }
|;|get %s() { return this._%s; }
set %s(value){ this._%s = value; }
마지막 부분은 각각 설명하면 다음과 같다.
this.%s = object[%s.FIELD%s] : 생성자 내부 필드 지정 부분
static get FIELD_%s() { return ‘%s’; } : static field 필드 템플릿
get %s() { return this.%s; } : getter 템플릿
set %s(value){ this.%s = value; } : setter 템플릿
순서에 유의하여 변환 변수 대입하여 js 용 sprintf 에 대입하여 처리하도록 하자! 순서대로 잘 넣어야 한다.
여기서 Model extends 부분의 클래스는 다음과 같다.
Model.js
const logger = require(`${__dirname}/../../lib/logger`)();
/**
* SMC PRO 클래스
*/
class Model {
/**
* SMCPRO 생성자
* @param object
*/ constructor(object) {
}
/**
* 필드 이름
* @param fieldName
* @returns {*}
*/
get(fieldName) {
return this[`_${fieldName.toLowerCase()}`];
}
/**
* * @param fieldName
* @param value
*/
set(fieldName, value) {
this[`_${fieldName.toLowerCase()}`] = value;
}
static fields(Class) {
const arrFields = [];
let fields = null;
if (Class) {
fields = Object.getOwnPropertyNames(Class);
for (const idx in fields) {
if (fields[idx].indexOf('FIELD') !== -1) {
arrFields.push(Class[fields[idx]]);
} } } else {
fields = Object.getOwnPropertyNames(this);
for (const idx in fields) {
if (fields[idx].indexOf('FIELD') !== -1) {
arrFields.push(this[fields[idx]]);
} } } return arrFields;
}
values(Class) {
const arrValues = [];
const fields = Object.getOwnPropertyNames(Class);
for (const idx in fields) {
if (fields[idx].indexOf('FIELD') !== -1) {
arrValues.push(this.get(Class[fields[idx]]));
} }
return arrValues;
}
checkFields(fields) {
let isCheckResult = true;
for (const idx in fields) {
if (this.get(fields[idx]) === null || this.get(fields[idx]) === undefined) {
isCheckResult = false;
break;
} } return isCheckResult;
}}// end of class
module.exports = Model;
Model.js의 class Model 는 다음 메소드를 미리 구현해 두었다.
get(fieldName) : 하위 클래스에 FIELD 로 정의된 정적 변수를 이용하여 실제 객체 내용에 접근하여 값 리턴
set(fieldName, value) : 하위 클래스에 FIELD 로 정의된 정적 변수를 이용하여 실제 객체 내용에 접근하여 값 대입
그 밖에 기본 클래스 다룰때 필요한 메소드 구현 해둠
genDTO index.js
이 모듈에서 사용하는 의존성 패키지는 다음과 같다.
const sprintf = require('sprintf-js').sprintf;
const fs = require('fs');
const dtoClassFieldsName = [];
const init = function () {
const templateData = fs.readFileSync(`${__dirname}/template/es6ClassType1-forRDB.txt`, 'utf-8').split('|;|');
const templateClassTotal = templateData[0];
const templateFieldDefiniation = templateData[1];
const templateStaticFieldDefiniation = templateData[2];
const templateGetterSetterDefiniation = templateData[3];
// console.log('template: ', templateData);
function GenDTO() {
// 객체 생성
}
const instance = new GenDTO();
instance.genClass = function (className, fieldNames, dtoDirPath) {
console.log('인자 검사 수행');
if (!className || !fieldNames) {
throw new Error('Parameters Error');
} else if (!Array.isArray(fieldNames) && typeof fieldNames !== 'string') {
throw new Error('Parameters Error - fieldNames type invalid');
} console.log('인자 전처리 작업');
if (typeof fieldNames === 'string') {
fieldNames = fieldNames.split(',');
fieldNames.forEach(function (elem, idx) {
fieldNames[idx] = elem.trim();
}); } if (!dtoDirPath) {
dtoDirPath = './';
} // 처리작업 수행
console.log('클래스 생성 ... 시작');
const result = instance.doGenClass(className, fieldNames);
fs.writeFileSync(`${dtoDirPath}/${className}.js`, result);
console.log('클래스 생성 ... 완료');
};
instance.doGenClass = function (className, fieldNames) {
const classNameResult = className;
const fieldDefinitionResult = instance.doGenFieldDefinition(className, fieldNames);
const staticFieldDefinitionResult = instance.doGenStaticFieldDefinition(className, fieldNames);
const getterSetterDefinitionResult = instance.doGenGetterSetterDefinition(className, fieldNames);
return sprintf(templateClassTotal, classNameResult, fieldDefinitionResult, staticFieldDefinitionResult, getterSetterDefinitionResult, classNameResult);
};
instance.doGenFieldDefinition = function (className, fieldNames) {
// console.log('doGenFieldDefinition');
// console.log('className>>', className); // console.log('fieldNames>>', fieldNames); let result = '';
let tabHead = '';
fieldNames.forEach((elem, idx) => {
result += sprintf(`${tabHead}${templateFieldDefiniation}`, elem.toLowerCase(), className, elem);
if (idx === 0) {
tabHead = ' ';
} }); return result;
};
instance.doGenStaticFieldDefinition = function (className, fieldNames) {
// console.log('doGenStaticFieldDefinition');
// console.log('className>>', className); // console.log('fieldNames>>', fieldNames); let result = '';
let tabHead = '';
fieldNames.forEach((elem, idx) => {
result += sprintf(`${tabHead}${templateStaticFieldDefiniation}`, elem, elem);
if (idx === 0) {
tabHead = ' ';
} }); return result;
};
instance.doGenGetterSetterDefinition = function (className, fieldNames) {
// console.log('doGenStaticFieldDefinition');
// console.log('className>>', className); // console.log('fieldNames>>', fieldNames); let result = '';
let tabHead = '';
fieldNames.forEach((elem, idx) => {
result += sprintf(`${tabHead}${templateGetterSetterDefiniation}`, elem.toLowerCase(), elem.toLowerCase(), elem.toLowerCase(), elem.toLowerCase());
if (idx === 0) {
tabHead = ' ';
} }); return result;
};
return instance;
};
module.exports = init();
배포
git: TBD
이 부분 Model.js 의존 관계 해결할 경우 수행 하도록 하겠습니다.
결론
이러한 방식을 통해 차후 프로젝트 생성 내가 주로 하는 패턴 구성으로 각종 설정만 달리하여 구성 요소 자동 생성하는 프로젝트 생성기로 확장해도 될 것 같댜.(필요하다 시급히!! 매번 직접 설치 후 설정 변경 너무 힘들고 귀찮아…)
이쯤에서 다른 프로젝트 생성기에서 나오는 express-generator 속을 보고 싶다.
Model 부분은 상속받는 코드도 마찬가지로 생성해주는 기능이 있어야 할 것 같다.
es6에서 인정하는 객체의 정의가 불충분 할 것 같은데… 이부분 알아보자
객체의 조건: 생성자, 소멸자, 복사 생성자, 대입연산자, getter&setter, 비교연산자 오버라이딩 등(C++ 기준), 더 필요한 것은 없을까?
Excel.WorkBook() -> workBook.addSheet -> sheet.columns 추가 ->
sheet.addRow 추가 -> workBook.xlsx.write or 다른 작업 수행
파일 읽기
엑셀 파일 읽기 기능
예시 데이터: test2.xlsx
sheet01
sheet02
수행 코드
const Excel = require('exceljs');
const workbook = new Excel.Workbook();
/// 파일 읽기 수행
workbook.xlsx.readFile('test2.xlsx').then(function(){
// 읽어 작업 하는 객체는 workbook 으로 진행하면 됨
});
파일 생성
엑셀 파일 생성 기능
const Excel = require('exceljs');
파일 매칭시 JSON으로 연결되는 부분이 중요했다.
다른 여타 excel
파일 쓰기
json 배열을 엑셀 파일 정적 파일 출력
수행 코드
const Excel = require('exceljs');
주의할 내용은 sheet.columns 배열 처리 부분으로 배열 자체를 한번 대입 시켜줄때 내부 헤더 객체로 변환됨.
sheet.columns = [] 후에 sheet.columns.push({…}) 형태로 추가할 경우 제대로 헤더가 작동하지 않는 경우가 있음
이때 발생하는 에러
(node:9828) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): TypeError: column.equivalentTo is not a function
(node:9828) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
요런 에러가 발생함… 알아내는데 한참 걸렸다는거…
혹시나 해서 디버깅도구 이용하여 미리 헤더 배열을 완성시킨 상태에서 대입 시킨 것과 미리 배열 할당한 상태에서 push 를 이용해 동적으로 추가하는 경우 비교한 결과
전자의 경우가 제대로 내부 객체(추가 필드가 정의됨)로 완성된다는 것. //TODO
응용 기능 구현
NodeJS Express 서버 구현하는 사람이라면 아무래도 ExcelJS 패키지 기능을 이용하여 요청/응답에 맞는 서비스를 제공하고 싶을 것이다. 특히 업무 중에 엑셀 다운로드 API 를 구현해야할 필요가 있었다.
// Create the workbook and populate it with data...
res.attachment("test.xlsx")
workbook.xlsx.write(res)
.then(function() {
res.end()
});
여기서 res.attachment 는 어떤 내용인가?
res.attachment([filename]), res.download(path, [filename], [callback]) : 클라이언트에게 파일을 표시하지 말고 다운로드 받으라고 전송함. filename을 주면 파일 이름이 명시되며, res.attachment는 헤더만 설정하므로 다운로드를 위한 node 코드가 따로 필요핟.
출처: http://luckyyowu.tistory.com/346