Created
November 14, 2022 21:34
-
-
Save samoshkin/9333b148721ecec45c47a08531d12221 to your computer and use it in GitHub Desktop.
Node application entry point and runner based on "redux-saga" library
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const Sentry = require('@sentry/node'); | |
const { | |
fork, | |
} = require('redux-saga/effects'); | |
async function init() { | |
// create any services and run all startup activites (e.g start HTTP server, connect to Db, etc) | |
const dbConn = await mongoDb.connect(config.services.mongo); | |
Basic.db = dbConn; | |
// connect to Redis | |
const redis = new RedisClient(config.services.redis); | |
await redis.connect(); | |
// init cache | |
const cache = new Cache(config.services.redis); | |
await cache.connect(); | |
User.cache = cache; | |
// etc | |
const stop = async (reason) => { | |
logger.info(`Gracefully stopping process. Reason: '${reason}'`); | |
// gracefully stop all your services | |
await server.stop(); | |
await mongoDb.disconnect(); | |
await redis.disconnect(); | |
await eventBus.stop(); | |
}; | |
return { | |
// tell your services, will be used for DI purposes | |
services: { | |
config, | |
eventBus, | |
cache, | |
redis, | |
dbConn, | |
pycoreClient, | |
pycoreClientNew, | |
userService, | |
server, | |
io: server.io, | |
}, | |
// graceful shutdown callback and shutdown timeout | |
stop, | |
stopTimeout: 10000, | |
// last chance to handle unhandled error before the process will be terminated | |
async onError(err) { | |
Sentry.captureException(err, { level: 'fatal' }); | |
await Sentry.close(2000); | |
}, | |
// application root process | |
appSaga, | |
}; | |
} | |
function* appSaga() { | |
// fork any number of application-specific sagas | |
// application logic comes here | |
yield fork(...); | |
yield fork(...); | |
// resolve any services from saga context (sort of service registry pattern) | |
const conn = yield getContext('mongoDb'); | |
} | |
module.exports = { | |
init, | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { run } = require('./app-runner'); | |
const app = require('./app'); | |
// application entry point | |
// $ node ./main.js | |
run(app); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { | |
call, | |
take, | |
race, | |
delay, | |
} = require('redux-saga/effects'); | |
const { | |
runSaga, | |
} = require('redux-saga'); | |
const noop = () => {}; | |
const { logger } = require('../../logger'); | |
const { | |
fromEvent, | |
createMulticastChannel, | |
} = require('../utils/saga'); | |
const { | |
openScope, | |
} = require('./effects'); | |
async function run(app) { | |
const channel = createMulticastChannel(); | |
const options = { | |
channel, | |
dispatch: (output) => { | |
channel.put(output); | |
}, | |
}; | |
// run root application process (aka PID=1) | |
const task = runSaga(options, rootSaga, app); | |
await task.toPromise(); | |
// root process ran to the end, terminate process with zero exit code | |
// eslint-disable-next-line no-process-exit | |
process.exit(0); | |
} | |
function* rootSaga({ init: appInit }) { | |
let onErrorHandler; | |
try { | |
const { | |
services, | |
appSaga, | |
stop, | |
stopTimeout, | |
onError, | |
} = yield call(appInit); | |
onErrorHandler = onError; | |
// run app until signaled to stop, or fails due to error | |
const [, shutdownSignal] = yield race([ | |
openScope({ context: services, saga: appSaga }), | |
call(waitForGracefulShutdown), | |
call(waitForUnhandledError), | |
]); | |
// gracefully stop the app (respects stopTimeout) | |
yield race([ | |
call(stop, shutdownSignal || 'finished'), | |
delay(stopTimeout), | |
]); | |
} catch (err) { | |
// terminate process immediately in case of unhandler error | |
yield call(terminateProcess, err, onErrorHandler); | |
} | |
} | |
function* waitForGracefulShutdown() { | |
const [sigint, sigterm] = yield race([ | |
take(fromEvent(process, 'SIGINT')), | |
take(fromEvent(process, 'SIGTERM')), | |
]); | |
return (sigint || sigterm); | |
} | |
function* waitForUnhandledError() { | |
const [uncaughtExceptionErr, unhandledRejectionErr] = yield race([ | |
take(fromEvent(process, 'uncaughtException')), | |
take(fromEvent(process, 'unhandledRejection')), | |
]); | |
throw (uncaughtExceptionErr || unhandledRejectionErr); | |
} | |
async function terminateProcess(err, onError = noop) { | |
logger.error('Terminate process:', err); | |
try { | |
await onError(err); | |
} finally { | |
// eslint-disable-next-line no-process-exit | |
process.exit(1); | |
} | |
} | |
module.exports = { | |
run, | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment