ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • express 와 singleton 패턴을 사용한 api server 구현
    Tech/NodeJs 2020. 1. 11. 13:05
    반응형

    MSA 와 무중단 배포 특성에 맞는 api server 를 구축 해보도록한다. 

     

    //server.js
    'use strict'
    
    const express = require('express')
    const http = require('http')
    const cookieParser = require('cookie-parser')
    //http.Server 를 통해 확장하여 싱글톤 패턴으로 만들도록 한다.
    //서버가 이중으로 생성되는것을 막기 위해 싱글톤으로 구현하도록 한다.
    //MSA 환경을 고려했을때 일관성있게 분산된 서버들의 상태를 관리하기 위함이다.
    //즉 생성자 내부에서 express 생성을 한번만 생성하도록 싱글톤 패턴을 적용하는 기법이다.
    class ApiServer extends http.Server {
        constructor(config) {
            const app = express()
            super(app)
            this.config = config
            this.app = app
            //현재 연결된 커넥션의 상태를 관리하기 위해 set 으로 관리한다.
            this.currentConnections = new Set()
            //무중단 배포 환경에서 사용중인 커넥션들을 관리하기 위해 선언
            this.busy = new WeakSet()
            //해당 서버가 무중단으로 배포되었을때 중단되어지는 상태인지 관리하기 위해 선언
            this.stopping = false
        }
    
        //비동기 메서드로서 시작 메서드를 생성한다
        async start() {
            this.app.use((req, res, next) => {
                //현재 요청 소켓을 커넥션 관리 변수(busy)에 담는다.
                this.busy.add(req.socket)
                //finish 로 응답 받았을 경우  커넥션 관리에서 삭제 한다
                res.on('finish', () => {
                    if (this.stopping) req.socket.end()
                    this.busy.delete(req.socket)
                })
                next()
            })
    
            this.app.use(cookieParser())
            //헬스체크를 위해 추가
            this.app.get('/_health', (req, res) => {
                res.sendStatus(200)
            })
            //에러 처리
            this.app.use((err, req, res, next) => {
                res.status(500).send(generateApiError('Api::Error'))
            })
    
            //커넥션에 대해 관리
            this.on('connection', c => {
                this.currentConnections.add(c)
                c.on('close', () => this.currentConnections.delete(c))
            })
            return this
        }
    
        shutdown() {
            //종료중인가?
            if (this.stopping) {
                return
            }
    
            this.stopping = true
            //종료(정상조료)
            this.close(() => {
                process.exit(0)
            })
            //종료되는 동안 블로킹되지 않도록 하기 위해 setTimeout 사용
            this.setTimeout(() => {
                console.error('비정상 종료 (강제종료)')
                process.exit(1)
            }, this.config.shutdownTimeout).unref()
    
            //현재 커넥션이 있을 경우 
            if (this.currentConnections.size > 0) {
                console.log(`현재 동시 접속중인 연결 (${this.currentConnections.size}) 을 대기중입니다.`)
                for (const con of this.currentConnections) {
                    //단순 접속자일 경우에는 종료 시키도록 한다.
                    if (!this.busy.has(con)) {
                        con.end()
                    }
                }
            }
        }
    
    }
    
    //usage:
    //초기화 (start 를 호출 하기 때문에 async 사용)
    const init = async (config = {}) => {
        //새로운 서버를 생성
        const server = new ApiServer(config)
        return await server.start()
    }
    
    module.exports = {
        init
    }
    //main.js
    'use strict'
    
    const {
        init
    } = require('./server')
    const {
        getConfig
    } = require('./config')
    const env = process.env.NODE_ENV
    const main = async () => {
        const config = await getConfig()
        const server = await init()
        server.listen(config.port, () => {
            console.log(`server listening on port ${config.port}`)
        })
        process.on('SIGTERM', () => server.shutdown())
        process.on('SIGINT', () => server.shutdown())
    }
    //config.js
    'use strict'
    
    module.config = {
        getConfig: () => {
            return {}
        }
    }
    반응형

    'Tech > NodeJs' 카테고리의 다른 글

    how to change function to arrow function  (0) 2020.01.11
    static method factory pattern  (0) 2020.01.10
    TDD - frameworks 소개  (0) 2020.01.10
    file  (0) 2020.01.10
    destructing  (0) 2020.01.10
Designed by Tistory.