본문 바로가기

NodeJs

express 와 singleton 패턴을 사용한 api server 구현

반응형

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 {}
    }
}
반응형

'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