diff --git a/src/infrastructure/applications/rest/IRestAppModuleConfig.ts b/src/infrastructure/applications/rest/IRestAppModuleConfig.ts index 43432d5..8badaa1 100644 --- a/src/infrastructure/applications/rest/IRestAppModuleConfig.ts +++ b/src/infrastructure/applications/rest/IRestAppModuleConfig.ts @@ -7,4 +7,5 @@ export interface IRestAppModuleConfig extends IAppModuleConfig { allowMethods?: string[], allowHeaders?: string[], }, + gracefulEnabled?: boolean, } diff --git a/src/infrastructure/applications/rest/RestApplication.ts b/src/infrastructure/applications/rest/RestApplication.ts index 1667ddf..ea41cbe 100644 --- a/src/infrastructure/applications/rest/RestApplication.ts +++ b/src/infrastructure/applications/rest/RestApplication.ts @@ -123,6 +123,12 @@ export class RestApplication extends BaseApplication { this._app.use(urlencoded({ extended: true, limit: this._config.requestSizeLimit })); } + protected initGraceful() { + if (this._config.gracefulEnabled) { + this._app.enableShutdownHooks(); + } + } + public async init() { await super.init(); @@ -137,6 +143,7 @@ export class RestApplication extends BaseApplication { this.initSentry(); this.initInterceptors(); this.initSettings(); + this.initGraceful(); } public async start() { diff --git a/src/infrastructure/applications/rest/config.ts b/src/infrastructure/applications/rest/config.ts index 8e5f5c1..3d95b6e 100644 --- a/src/infrastructure/applications/rest/config.ts +++ b/src/infrastructure/applications/rest/config.ts @@ -3,6 +3,8 @@ import baseConfig from '../base/config'; import {IRestAppModuleConfig} from './IRestAppModuleConfig'; import {ValidationExceptionFilterCustom} from './filters/ValidationExceptionFilterCustom'; import {RequestExecutionExceptionFilter} from './filters/RequestExecutionExceptionFilter'; +import {GracefulController} from "./graceful/GracefulController"; +import {GracefulService} from "./graceful/GracefulService"; export default { ...baseConfig, @@ -17,6 +19,10 @@ export default { provide: APP_FILTER, useClass: RequestExecutionExceptionFilter, }, - ], + config.gracefulEnabled && GracefulService, + ].filter(Boolean), + controllers: [ + config.gracefulEnabled && GracefulController, + ].filter(Boolean), }), }; diff --git a/src/infrastructure/applications/rest/graceful/GracefulController.ts b/src/infrastructure/applications/rest/graceful/GracefulController.ts new file mode 100644 index 0000000..1ac6fc4 --- /dev/null +++ b/src/infrastructure/applications/rest/graceful/GracefulController.ts @@ -0,0 +1,18 @@ +import {Controller, Get, InternalServerErrorException} from "@nestjs/common"; +import {GracefulService} from "./GracefulService"; + +@Controller() +export class GracefulController { + constructor( + public service: GracefulService + ) { } + + @Get('health') + checkHealth() { + if (this.service.isShutdown) { + throw new InternalServerErrorException('Application is currently shutting down.'); + } + + return 'ok'; + } +} diff --git a/src/infrastructure/applications/rest/graceful/GracefulService.ts b/src/infrastructure/applications/rest/graceful/GracefulService.ts new file mode 100644 index 0000000..7670c1e --- /dev/null +++ b/src/infrastructure/applications/rest/graceful/GracefulService.ts @@ -0,0 +1,18 @@ +import {BeforeApplicationShutdown} from "@nestjs/common"; + +export class GracefulService implements BeforeApplicationShutdown { + public isShutdown = false; + + async beforeApplicationShutdown(signal?: string) { + this.isShutdown = true; + + if (process.env.K8S_READINESS_FAILURE_THRESHOLD && process.env.K8S_READINESS_PERIOD_SECONDS) { + const failureThreshold = parseInt(process.env.K8S_READINESS_FAILURE_THRESHOLD, 10); + const periodSeconds = parseInt(process.env.K8S_READINESS_PERIOD_SECONDS, 10); + + await new Promise((resolve) => { + setTimeout(resolve, failureThreshold * periodSeconds * 1000); + }); + } + } +}