2018년 12월 2일 일요일

[Node.js Project] es6 Class Generator - 클래스 생성기 만들

[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++ 기준), 더 필요한 것은 없을까?
  • 성능에 대해서는 고려하지 않고 내 편한대로 우선 만든 것.
  • 차후 안정화와 사용에 문제가 전혀 되지 않으면 나중에는 npm으로 배포 도전하겠다.

댓글 없음:

댓글 쓰기