ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 2019-08-30 개발일지
    개발일지 2019. 8. 30. 18:07

    현재 진행 중인 프로젝트에서 개발해야 할 큰 기능이 하나정도 남았는데 기획이 나오지 않아 진행을 임시 중단한 상태다.

     

    그래서 현재 개인 프로젝트를 만들어볼까 한다.

    생각 중인 건 (TypeScript + Node.js) + IOS 앱을 하나 만드려고 한다.

     

    대충 느낌은 푸시 알림이 메인이 되는 서비스이며, 선택한 내용에 대한 정보를 매 일 랜덤하게 하나씩 푸시를 발송한다.

     

    기능만으로 보면 간단해서 오래 걸리거나 하는 문제는 아니지만 처음이라 기획부터해서 어떠한 UI 컨셉을 잡아야하는지 감이 오질 않는다.

     

    뭐 어쨌든...

    그래서 일단 상세한 시나리오는 차차 생각하기로 하고 일단 서버의 기본 설정부터 하기로 했다.

     

    사실 타입스크립트 핸드북의 모든 챕터를 보고 시작하려고 했는데 도저히 진도가 안나가는데다가 요즘 즐겨보는 노마드코더 유튜브 채널의 니콜라스가 이런 얘기를 했다.

     

    '새로운 언어를 습득할 때 가장 빠른 방법은 그걸로 뭔가를 만들어보는거야. 그게 가장 빠름.

    문서나 튜토리얼 보느라 시간 낭비하지말고 그냥 바로 써봐. '

     

    이 얘기에 굉장히 공감이 되었다.

    사실 지금까지 언어나 프레임워크, 함수들을 공부하고 직접 사용해보면 결국 다시 찾아보는 행동의 반복들이었다.

    즉 그것에 대하여 전체 챕터 리딩을 한다고 해서 천재가 아닌 이상 그것을 머릿 속에 전부 저장할 수 있는게 아니라는 것이다.

    그래서  함수 챕터를 보는 도중에 그냥 바로 프로젝트로 만들어봤다.

     

    express_config.js

    const express = require('express');
    
    module.exports = () => {
        const app = express();
        
        app.use(express.urlencoded({ extended: true }));
        app.use(express.json());
        
        require('../app/routes/helloRoute')(app);
        
        return app;
    }

    app.js

    const expressConfig = require('./config/express_config');
    
    const app = expressConfig();
    
    app.listen(3000, () => {
        console.log('listening');
    }).on('error', err => {
        console.error(err);
    });

    예를 들어 위와 같은 express 설정 모듈이 있다고 할 때 이를 타입스크립트로 변경해보자.

     

     

    express_config.ts

    import express from 'express'
    import helloRoute from '../app/routes/helloRoute';
    
    class App {
        app: express.Application;
        
        static bootstap(): App {
            return new App();
        }
        
        constructor() {
            this.app = express();
            
            this.app.use(express.urlencoded({ extended: true }));
            this.app.use(express.json());
            
            helloRoute(this.app);
        }
    }
    
    export default App;

    app.ts

    import expressConfig from './config/express_config';
    
    const app = new expressConfig().app;
    
    app.listen(3000, () => {
        console.log('listening');
    }).on('error', err => {
        console.error(err);
    });

     

    어때유? 참 쉽쥬?

     

    기존 js에서 익명함수 기반으로 코드를 작성한 것을 ts의 클래스 기반으로 코드를 작성해봤다.

    두 코드의 큰 차이는 함수형 기반이냐 클래스 기반이냐로 구분지으면 되고, 동작 상 차이점은 없다.

     

     

    라우트랑 컨트롤러도 한번 만들어볼까?

     

    helloController.js

    exports.hello = (req, res) => {
        return res.status(200).send('안녕!');
    }

    helloRoute.js

    const helloController = require('../controllers/helloController');
    
    module.exports = (app) => {
        app.get('/hello', helloController.hello);
    }

     

    타입 스크립트로 바꿔보자!

     

    helloController.ts

    import express from 'express';
    
    export function hello(req: exrpess.Request, res: express.Response): express.Response {
        return res.status(200).send('안녕!');
    }

    helloRoute.ts

    import express from 'express';
    import * as helloController from '../controllers/helloController';
    
    export default (app: express.Application) => {
        app.get('/hello', helloController.hello);
    };

     

    쉽다! 어려운 건 없다!

    helloRoute.ts에서 helloController.ts를 모듈로 불러올 때 별칭을 준 이유는 helloController에서 함수들을 계속 export하므로 export한 모든 함수들을(*) helloController라는 별칭(as)을 부여하여 호출하기 위해서다.

     

    이후 시퀄라이즈 연동도 했고 커스텀 에러 객체나 공통 메시지 객체도 만들었다.

     

    customError.ts

    class CustomError extends Error {
        errorCode: number;
        date: Date;
        
        constructor(errCode: number, errMessage: string = '', ...params: Array<any>) {
            super(...params);
            
            if (Error.captureStackTrace) 
            {
                Error.captureStackTrace(this, CustomError);
            }
            
            // 디버깅 정보
            this.errorCode = errCode;
            this.date = new Date();
            if (errMessage && errMessage.length > 0)
            {
                this.message = errMessage;
            }
            else
            {
                switch (errCode) 
                {
                    case 1: 
                        this.name = 'Login Authority Error';
                        this.message = '로그인 후 이용해주세요.';
                        break;
                    case 2: 
                        this.name = 'Repitition Login Error';
                        this.message = '이미 로그인 된 사용자입니다.';
                        break;
                    default: 
                        this.name = 'Can Not Know Cause Error';
                        this.message = '알 수 없는 문제가 발생했습니다.';
                        break;
                }
            }
        }
    }
    
    export default CustomError;

    message.ts

    class Message {
        message: string;
        
        constructor() {
            this.message = '';
        }
    }
    
    class SuccessMessage extends Message {
        reponseData: object;
        
        constructor() {
            super();
            this.responseData = {};
        }
    }
    
    class ErrorMessage extends Message {
        errorCode: number;
        
        constructor() {
            super();
            this.errorCode = -1;
        }
    }

    sequelize.ts

    import { Sequelize } from 'sequelize-typescript';
    import { Dialect } from 'sequelize/types';
    import pg from 'pg';
    
    pg.defaults.parseInt8 = true; // bigint to number 타입 캐스팅
    
    class PGSequelize {
        pgs: Sequelize;
        
        constructor() {
            this.pgs = new Sequelize({
                database: 'dbname', 
                host: 'localhost', 
                port: '5432', 
                username: 'test', 
                password: 'test', 
                dialect: 'postgres' as Dialect 
                // 문자열을 바로 입력하면 Dialect 캐스팅을 할 필요가 없지만, 
                // 실제 코드는 DB 정보를 입력해놓은 config 모듈을 로드해서 객체를 호출하는 형식으로 사용하기 때문에
                // Dialect로 타입 캐스팅이 필요했다.
            });
        }
    }
    
    export default PGSequelize;

     

    시퀄라이즈 설정에서 dialect를 설정할 때 config 객체를 통해서 config.db.dialect 형식으로 문자열을 호출했는데 아래의 에러가 발생했다.

     

    Argument of type '{ database: string; username: string; password: string; host: string; port: number; dialect: string; }' is not assignable to parameter of type 'string'.

    내용은 string 유형의 인자 타입에 할당할 수 없다는 타입에러였다. 

    생성자에 설정이 어떻길래 그러는지 궁금해서 Sequelize 생성자를 뜯어보기로 했다.

     

     

    sequelize.d.ts

    총 4개의 생성자가 존재한다.

    생성자 중 SequelizeOptions를 따라가 뜯어보자.

     

    sequelize-options.d.ts

    SequelizeOptions는 Options를 확장하여 추가로 속성들의 타입이 정의되어 있다.

    SequelizeOptions는 인터페이스로 구성되어 있는데, 내가 알고 싶은 dialect 속성에 대한 정의가 없었다.

    아마 확장하고 있는 Options를 따라가보면 될 것 같다.

     

    sequelize.d.ts

    머임 다시 sequelize.d.ts로 돌아왔네...

    Options는 인터페이스로써, sequelize.d.ts에 정의되어 있었다.

    이 곳에 dialect가 정의되어 있었고, 그 타입은 Dialect였다.

     

    Dialect는 문자열인데, 시퀄라이즈는 따로 Dialect라는 타입을 정의했다.

    Dialect는 string 값의 커스텀 타입이었다.

    string은 string인데 Dialect라는 타입을 지정해서 사용하니 내가 호출한 객체의 string 값이 인식되지 않았다.

     

    아마 이 부분은 참조(Reference)에 연관된 문제인거 같다.

     

     

    타입 스크립트로 잠깐 작업해보니 자바같으면서도 자바처럼 루즈한 느낌은 없었다.

     

    타입스크립트는 공부를 오래 한 것도 아니고 그냥 핸드북 보고, 패키지 까보고 한 게 다 인데 개발이 되니까 재밌다.

    '개발일지' 카테고리의 다른 글

    2019-09-03 개발일지  (0) 2019.09.03
    2019-09-02 개발일지  (0) 2019.09.03
    2019-08-29 개발일지  (0) 2019.08.29
    2019-08-28 개발일지  (0) 2019.08.28
    2019-08-27 개발일지  (0) 2019.08.27
Designed by Tistory.